/*=========================================================================

     wingui.cpp  -  Copyright (C) 1993-2005 by Don Cross

     Contains WinMain() for Win32 GUI version of Chenard.

=========================================================================*/

#include <string.h>
#include <stdio.h>
#include <process.h>
#include <io.h>
#include <assert.h>
#include <direct.h>

#include "chess.h"
#include "chenga.h"
#include "winchess.h"
#include "winguich.h"
#include "resource.h"
#include "profiler.h"

const char ChessProgramName[] = CHESS_PROGRAM_NAME;

char Global_GameFilename [256] = "*.PGN";

bool Global_ResetGameFlag    = false;
bool Global_ClearBoardFlag   = false;
int      Global_AnalysisType     = 0;   //0=none, 1=best path, 2=current path
bool Global_GameOpenFlag     = false;
bool Global_GameSaveFlag     = false;
bool Global_HaveFilename     = false;
bool Global_GameModifiedFlag = false;
bool Global_TacticalBenchmarkFlag = false;
bool Global_GameOverFlag     = true;   // It's over before it begins!
bool Global_UndoMoveFlag     = false;
bool Global_RedoMoveFlag     = false;
bool Global_AllowResignFlag  = false;
bool Global_AnimateMoves     = true;
bool Global_EnableOppTime    = true;
bool Global_HiliteMoves      = true;
bool Global_AutoSingular     = false;
bool Global_ExtendedSearch   = false;
bool Global_EnableGA = false;
bool Global_EnableEndgameDatabaseGen = false;
bool Global_EnableBoardBreathe  = true;
extern int      Global_EndgameDatabaseMode;
int      Global_GA_NumGenes = 200;

#if SUPPORT_INTERNET
    WSADATA  Global_WsaData;
    bool Global_NetworkInit   =  false;
    SOCKET   Global_MySocket      =  INVALID_SOCKET;
    char     Global_MyHostName [256];
    hostent  Global_MyHostEnt;
    bool Global_InternetPlayersReady = false;
    BOOL Global_ServerMode = FALSE;
#endif // SUPPORT_INTERNET

char DefaultServerIpAddress [256];

ChessUI_win32_gui *Global_UI = 0;
ChessBoard Global_Board;

BoardEditRequest Global_BoardEditRequest;

static char WhiteTimeLimit [64] = "10";
static char BlackTimeLimit [64] = "10";

char TitleBarText_WhitesTurn[128] = CHESS_PROGRAM_NAME " - White's turn";
char TitleBarText_BlacksTurn[128] = CHESS_PROGRAM_NAME " - Black's turn";
char TitleBarText_GameOver[128]   = CHESS_PROGRAM_NAME " - game over";

char GameSave_filename[] = "chess.gam";

LRESULT CALLBACK ChessWndProc ( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );


bool Global_AbortFlag = false;
static bool globalImmedQuitFlag = false;
static int SoundEnableFlag = 1;
HWND   HwndMain;
HMENU  HmenuMain;
HINSTANCE global_hInstance = NULL;
DWORD global_windowFlags = WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU | WS_OVERLAPPED;

bool PreDefinedPlayerSides = false;
bool PreDefinedWhiteHuman  = false;
bool PreDefinedBlackHuman  = false;
DefPlayerInfo DefPlayer;


static void EnableEditPieces ( HWND hwnd, bool enable )
{
    EnableWindow ( GetDlgItem(hwnd,IDC_EDIT_PAWN),   enable );
    EnableWindow ( GetDlgItem(hwnd,IDC_EDIT_KNIGHT), enable );
    EnableWindow ( GetDlgItem(hwnd,IDC_EDIT_BISHOP), enable );
    EnableWindow ( GetDlgItem(hwnd,IDC_EDIT_ROOK),   enable );
    EnableWindow ( GetDlgItem(hwnd,IDC_EDIT_QUEEN),  enable );
    EnableWindow ( GetDlgItem(hwnd,IDC_EDIT_KING),   enable );
}


BOOL CALLBACK About_DlgProc (
    HWND hwnd,
    UINT msg,
    WPARAM wparam,
    LPARAM lparam )
{
    BOOL result = FALSE;

    switch ( msg )
    {
        case WM_INITDIALOG:
        {
            SetFocus ( GetDlgItem(hwnd,IDOK) );
        }
        break;

        case WM_COMMAND:
        {
            switch ( wparam )
            {
                case IDOK:
                {
                    EndDialog ( hwnd, IDOK );
                    result = TRUE;
                }
                break;
            }
        }
        break;
    }

    return result;
}


BOOL CALLBACK EnterFen_DlgProc (
    HWND    hwnd,
    UINT    msg,
    WPARAM  wparam,
    LPARAM  lparam )
{
    BOOL    result = FALSE;
    char    buffer [500];

    switch (msg) {
        case WM_INITDIALOG:
            SetFocus ( GetDlgItem(hwnd,TB_EDIT_FEN) );
            break;

        case WM_COMMAND:
            switch (wparam) {
                case IDOK:
                    if (GetWindowText (GetDlgItem (hwnd,TB_EDIT_FEN), buffer, sizeof(buffer))) {
                        Global_BoardEditRequest.sendFen (buffer);
                    }
                    EndDialog (hwnd, wparam);
                    result = TRUE;
                    break;

                case IDCANCEL:
                    EndDialog (hwnd, wparam);
                    result = TRUE;
                    break;
            }
            break;
    }
    return result;
}


BOOL CALLBACK EditThinkTimes_DlgProc (
    HWND    hwnd,
    UINT    msg,
    WPARAM  wparam,
    LPARAM  lparam )
{
    BOOL result = FALSE;
    char buffer [128];

    switch ( msg )
    {
        case WM_INITDIALOG:
        {
            // Figure out which players are computer players.
            // Disable think time edit boxes for non-computer players.
            if ( Global_UI->queryWhitePlayerType() == DefPlayerInfo::computerPlayer )
            {
                INT32 timeLimit = Global_UI->queryWhiteThinkTime();
                sprintf ( buffer, "%0.1lf", double(timeLimit) / 100.0 );
                SetWindowText ( GetDlgItem(hwnd,IDC_EDIT_WHITE_TIME), buffer );
            }
            else
            {
                SetWindowText ( GetDlgItem(hwnd,IDC_EDIT_WHITE_TIME), "n/a" );
                EnableWindow ( GetDlgItem(hwnd,IDC_EDIT_WHITE_TIME), FALSE );
            }

            if ( Global_UI->queryBlackPlayerType() == DefPlayerInfo::computerPlayer )
            {
                INT32 timeLimit = Global_UI->queryBlackThinkTime();
                sprintf ( buffer, "%0.1lf", double(timeLimit) / 100.0 );
                SetWindowText ( GetDlgItem(hwnd,IDC_EDIT_BLACK_TIME), buffer );
            }
            else
            {
                SetWindowText ( GetDlgItem(hwnd,IDC_EDIT_BLACK_TIME), "n/a" );
                EnableWindow ( GetDlgItem(hwnd,IDC_EDIT_BLACK_TIME), FALSE );
            }
        }
        break;

        case WM_COMMAND:
        {
            switch ( wparam )
            {
                case IDOK:
                {
                    if ( Global_UI )
                    {
                        double timeLimit = 0;
                        if ( GetWindowText ( GetDlgItem(hwnd,IDC_EDIT_WHITE_TIME), buffer, sizeof(buffer) ) &&
                            (timeLimit = atof(buffer)) >= 0.1 )
                        {
                            Global_UI->setWhiteThinkTime ( INT32(timeLimit * 100) );
                        }

                        if ( GetWindowText ( GetDlgItem(hwnd,IDC_EDIT_BLACK_TIME), buffer, sizeof(buffer) ) &&
                            (timeLimit = atof(buffer)) >= 0.1 )
                        {
                            Global_UI->setBlackThinkTime ( INT32(timeLimit * 100) );
                        }

                        EndDialog ( hwnd, IDOK );
                        result = TRUE;
                    }
                }
                break;

                case IDCANCEL:
                {
                    EndDialog ( hwnd, IDCANCEL );
                    result = TRUE;
                }
                break;
            }
        }
        break;
    }

    return result;
}


BOOL CALLBACK EditSquare_DlgProc (
    HWND hwnd,  // handle to dialog box
    UINT msg,           // message
    WPARAM wparam,  // first message parameter
    LPARAM lparam ) // second message parameter
{
    static SQUARE piece = EMPTY;
    static SQUARE side = EMPTY;
    BOOL result = TRUE;
    static SQUARE *squarePtr = 0;
    static WPARAM lastPiece = IDC_EDIT_PAWN;
    static WPARAM lastSide = IDC_EDIT_EMPTY;

    switch ( msg )
    {
        case WM_INITDIALOG:
        {
            squarePtr = (SQUARE *) (lparam);
            CheckRadioButton ( hwnd, IDC_EDIT_PAWN, IDC_EDIT_KING, (int)lastPiece );
            CheckRadioButton ( hwnd, IDC_EDIT_EMPTY, IDC_EDIT_BLACK, (int)lastSide );
            EnableEditPieces ( hwnd, lastSide != IDC_EDIT_EMPTY );
        }
        break;

        case WM_COMMAND:
        {
            switch ( wparam )
            {
                case IDOK:
                {
                    if ( squarePtr )
                    {
                        if ( side == EMPTY )
                            *squarePtr = EMPTY;
                        else
                            *squarePtr = PieceLookupTable.sval [ piece | side ];
                    }

                    EndDialog ( hwnd, IDOK );           
                }
                break;

                case IDCANCEL:
                {
                    EndDialog ( hwnd, IDCANCEL );           
                }
                break;

                case IDC_EDIT_PAWN:    piece = P_INDEX;   lastPiece = wparam;   break;
                case IDC_EDIT_KNIGHT:  piece = N_INDEX;   lastPiece = wparam;   break;
                case IDC_EDIT_BISHOP:  piece = B_INDEX;   lastPiece = wparam;   break;
                case IDC_EDIT_ROOK:    piece = R_INDEX;   lastPiece = wparam;   break;
                case IDC_EDIT_QUEEN:   piece = Q_INDEX;   lastPiece = wparam;   break;
                case IDC_EDIT_KING:    piece = K_INDEX;   lastPiece = wparam;   break;

                case IDC_EDIT_EMPTY:
                {
                    side = EMPTY;
                    lastSide = wparam;
                    EnableEditPieces ( hwnd, FALSE );            
                }
                break;

                case IDC_EDIT_WHITE:
                {
                    side = WHITE_IND;
                    lastSide = wparam;
                    EnableEditPieces ( hwnd, TRUE );         
                }
                break;

                case IDC_EDIT_BLACK:
                {
                    side = BLACK_IND;
                    lastSide = wparam;
                    EnableEditPieces ( hwnd, TRUE );         
                }
                break;
            }
        }
        break;

        default:
            result = FALSE;
            break;
    }

    return result;
}


static bool EditSquareDialog ( HWND hwnd, SQUARE &square )
{
    INT_PTR result = DialogBoxParam ( 
        global_hInstance,
        MAKEINTRESOURCE(IDD_EDIT_SQUARE),
        hwnd,
        DLGPROC(EditSquare_DlgProc),
        LPARAM(&square) );

    bool choseSomething = false;

    switch ( result )
    {
        case IDOK:
            choseSomething = true;
            break;
    }

    return choseSomething;
}


BOOL CALLBACK PromotePawn_DlgProc (
    HWND hwnd,          // handle to dialog box
    UINT msg,           // message
    WPARAM wparam,  // first message parameter
    LPARAM lparam ) // second message parameter
{
    BOOL result = FALSE;
    BOOL rook_rb, bishop_rb, knight_rb;
    static SQUARE *promPtr;

    switch ( msg )
    {
        case WM_INITDIALOG:
        {
            // Turn on the 'queen' radio button...
            CheckRadioButton ( hwnd, RB_QUEEN, RB_KNIGHT, RB_QUEEN );

            // Place to put promoted pawn data...
            promPtr = (SQUARE *) lparam;
            result = TRUE;
        }
        break;

        case WM_COMMAND:
        {
            switch ( wparam )
            {
                case IDOK:
                {
                    rook_rb   = IsDlgButtonChecked ( hwnd, RB_ROOK );
                    bishop_rb = IsDlgButtonChecked ( hwnd, RB_BISHOP );
                    knight_rb = IsDlgButtonChecked ( hwnd, RB_KNIGHT );

                    if ( rook_rb )
                        *promPtr = R_INDEX;
                    else if ( bishop_rb )
                        *promPtr = B_INDEX;
                    else if ( knight_rb )
                        *promPtr = N_INDEX;
                    else // assume the queen radio button is selected
                        *promPtr = Q_INDEX;

                    EndDialog ( hwnd, IDOK );
                }
                break;
            }
        }
        break;
    }

    return result;
}


#if SUPPORT_INTERNET


BOOL InitializeNetwork ( HWND hwnd )
{
    if ( !Global_NetworkInit )
    {
        int startRet = WSAStartup ( 0x202, &Global_WsaData );
        if ( startRet == 0 )
        {
            Global_NetworkInit = true;
            if ( gethostname ( Global_MyHostName, sizeof(Global_MyHostName) ) != 0 )
            {
                MessageBox ( hwnd, "Could not determine local host name!", "Network Error", MB_OK | MB_ICONERROR );
                return FALSE;
            }
            else
            {
                hostent *myHostInfo = gethostbyname (Global_MyHostName);
                if ( !myHostInfo )
                {
                    MessageBox ( hwnd, "Could not obtain local host information!", "Network Error", MB_OK | MB_ICONERROR );
                    return FALSE;
                }
                else
                {
                    Global_MyHostEnt = *myHostInfo;
                    Global_MySocket = socket ( AF_INET, SOCK_STREAM, 0 );
                    if ( Global_MySocket == INVALID_SOCKET )
                    {
                        MessageBox ( hwnd, "Could not create socket!", "Network Error", MB_OK | MB_ICONERROR );
                        return FALSE;
                    }
                }
            }
        }
        else
        {
            const char *errorString = "Unknown network error";
            switch ( startRet )
            {
                case WSASYSNOTREADY:
                    errorString = "Network subsystem not ready";
                    break;

                case WSAVERNOTSUPPORTED:
                    errorString = "Unsupported version of WinSock";
                    break;

                case WSAEINPROGRESS:
                    errorString = "A blocking WinSock 1.1 operation is in progress";
                    break;

                case WSAEPROCLIM:
                    errorString = "Too many WinSock tasks!";
                    break;
            }

            MessageBox ( hwnd, errorString, "Network Error", MB_OK | MB_ICONERROR );
            return FALSE;
        }
    }

    return TRUE;
}


BOOL ConnectToChenardServer ( 
    HWND defPlayerHwnd,
    DefPlayerInfo &playerInfo, 
    const char *remoteIpAddressString )
{
    char tempString [256];

    if ( !InitializeNetwork(defPlayerHwnd) )
        return FALSE;

    unsigned remoteIp[4] = { 0, 0, 0, 0 };
    int numScanned = sscanf ( 
        remoteIpAddressString, 
        "%u.%u.%u.%u", 
        &remoteIp[0], &remoteIp[1], &remoteIp[2], &remoteIp[3] );

    if ( numScanned != 4 || 
        remoteIp[0] > 0xff || remoteIp[1] > 0xff || remoteIp[2] > 0xff || remoteIp[3] > 0xff )
    {
        MessageBox ( defPlayerHwnd, "Invalid IP address entered!", "Error", MB_OK | MB_ICONERROR );
        SetFocus ( GetDlgItem(defPlayerHwnd,IDC_REMOTE_IP) );
        return FALSE;
    }

    sockaddr_in connectAddr;
    memset ( &connectAddr, 0, sizeof(connectAddr) );
    connectAddr.sin_family = AF_INET;
    connectAddr.sin_port = htons(InternetChessPlayer::QueryServerPortNumber());

    connectAddr.sin_addr.S_un.S_un_b.s_b1 = remoteIp[0];
    connectAddr.sin_addr.S_un.S_un_b.s_b2 = remoteIp[1];
    connectAddr.sin_addr.S_un.S_un_b.s_b3 = remoteIp[2];
    connectAddr.sin_addr.S_un.S_un_b.s_b4 = remoteIp[3];

    int result = connect ( Global_MySocket, (sockaddr *) &connectAddr, sizeof(connectAddr) );
    if ( result != 0 )
    {
        sprintf ( tempString, "Error %d connecting to remote Chenard", int(WSAGetLastError()) );
        MessageBox ( defPlayerHwnd, tempString, "Network Error", MB_OK | MB_ICONERROR );
        return FALSE;
    }

    // Now that we are connected to the remote Chenard, 
    // receive from it our player definition:

    UINT32 packetSize = 0;
    int bytesReceived = recv ( Global_MySocket, (char *)&packetSize, 4, 0 );
    if ( bytesReceived != 4 )
    {
        MessageBox ( defPlayerHwnd,
            "Error receiving player definition packet size!",
            "Network Error",
            MB_OK | MB_ICONERROR );

        return FALSE;
    }

    char inMessageType [16];
    memset ( inMessageType, 0, sizeof(inMessageType) );
    bytesReceived = recv ( Global_MySocket, inMessageType, 8, 0 );
    if ( bytesReceived != 8 || strncmp(inMessageType,"players ",8) != 0 )
    {
        MessageBox ( defPlayerHwnd, 
            "Error receiving player definition header!",
            "Network Error",
            MB_OK | MB_ICONERROR );

        return FALSE;
    }

    char playerDef [16];
    memset ( playerDef, 0, sizeof(playerDef) );
    bytesReceived = recv ( Global_MySocket, playerDef, 2, 0 );
    if ( bytesReceived != 2 )
    {
        MessageBox ( defPlayerHwnd,
            "Error receiving player definition structure!",
            "Network Error",
            MB_OK | MB_ICONERROR );

        return FALSE;
    }

    if ( playerDef[0] == 'I' )
    {
        playerInfo.whiteType = DefPlayerInfo::internetPlayer;
        playerInfo.whiteInternetConnect.commSocket = Global_MySocket;
        playerInfo.whiteInternetConnect.waitForClient = 0;
    }
    else
        playerInfo.whiteType = DefPlayerInfo::humanPlayer;

    if ( playerDef[1] == 'I' )
    {
        playerInfo.blackType = DefPlayerInfo::internetPlayer;
        playerInfo.blackInternetConnect.commSocket = Global_MySocket;
        playerInfo.blackInternetConnect.waitForClient = 0;
    }
    else
        playerInfo.blackType = DefPlayerInfo::humanPlayer;

    Global_InternetPlayersReady = true;
    return TRUE;
}


static uintptr_t ServerConnectThreadId = 0;

void ServerConnectThreadFunc ( void * )
{
    while ( !Global_InternetPlayersReady )
    {
        int result = listen ( Global_MySocket, 16 );
        if ( result != 0 )
        {
            break;  // !!!
        }

        sockaddr_in connectAddr;
        int connectAddrSize = sizeof(connectAddr);
        SOCKET connectSocket = accept ( 
            Global_MySocket, 
            (sockaddr *) &connectAddr, 
            &connectAddrSize );

        if ( connectSocket == INVALID_SOCKET )
        {
            break;   // !!!
        }

        InternetConnectionInfo *connectInfo = 0;
        int clientIsWhite = 0;
        if ( DefPlayer.whiteInternetConnect.waitForClient )
        {
            clientIsWhite = 1;
            connectInfo = &DefPlayer.whiteInternetConnect;
        }
        else if ( DefPlayer.blackInternetConnect.waitForClient )
            connectInfo = &DefPlayer.blackInternetConnect;      

        if ( connectInfo )
        {
            connectInfo->commSocket = connectSocket;
            connectInfo->waitForClient = 0;

            // Send player definition data to client.
            // We must express the data from *their* point of view.

            UINT32 packetSize = 10;
            result = send ( connectSocket, (const char *) &packetSize, 4, 0 );
            if ( result != 4 )
                break;

            result = send ( connectSocket, "players ", 8, 0 );
            if ( result != 8 )
                break;

            char playerDef [2];
            playerDef[0] = clientIsWhite ? 'H' : 'I';
            playerDef[1] = clientIsWhite ? 'I' : 'H';

            result = send ( connectSocket, playerDef, 2, 0 );
            if ( result != 2 )
                break;
        }

        Global_InternetPlayersReady = 
            !DefPlayer.whiteInternetConnect.waitForClient && 
            !DefPlayer.blackInternetConnect.waitForClient;
    }

    ServerConnectThreadId = 0;
}

#endif // SUPPORT_INTERNET


BOOL CALLBACK DefineChessPlayers_DlgProc (
    HWND hwnd,          // handle to dialog box
    UINT msg,           // message
    WPARAM wparam,      // first message parameter
    LPARAM lparam )     // second message parameter
{
   BOOL result = TRUE;
   const char *defaultTimeLimit = "2";

   switch ( msg )
   {
      case WM_INITDIALOG:
      {
#if SUPPORT_INTERNET
          Global_ServerMode = TRUE;
          DefPlayer.whiteInternetConnect.waitForClient = 0;
          DefPlayer.blackInternetConnect.waitForClient = 0;
          SetWindowText ( GetDlgItem(hwnd,IDC_REMOTE_IP), DefaultServerIpAddress );
#endif

          SetWindowText ( GetDlgItem(hwnd,TB_WTIME), WhiteTimeLimit );
          SetWindowText ( GetDlgItem(hwnd,TB_BTIME), BlackTimeLimit );

          EnableWindow ( GetDlgItem(hwnd,TB_W_PIPE_NAME), FALSE );
          EnableWindow ( GetDlgItem(hwnd,TB_B_PIPE_NAME), FALSE );

          bool whiteHuman = true;
          bool blackHuman = true;

          if (PreDefinedPlayerSides) {
              // We already know what the probable chess player settings should be.
              // Currently, this is because we have loaded a PGN file and found the player settings from it.
              // We will still show the dialog box so the user can override the settings.
              whiteHuman = PreDefinedWhiteHuman;
              blackHuman = PreDefinedBlackHuman;
          } else {
              // Choose randomly for the human player to play White or Black, 
              // and the computer to play the opposite side.
              whiteHuman = (ChessRandom(2) != 0);
              blackHuman = !whiteHuman;
          }

          if (whiteHuman) {
                // Turn on the 'human' radio button for white player...
                CheckRadioButton ( hwnd, RB_WHUMAN, RB_WCOMPUTER, RB_WHUMAN );

                // Disable think time for White computer player:
                EnableWindow ( GetDlgItem(hwnd,TB_WTIME), FALSE );
          } else {
                // Turn on the 'computer' radio button for white player...
                CheckRadioButton ( hwnd, RB_WHUMAN, RB_WCOMPUTER, RB_WCOMPUTER );
                SetFocus ( GetDlgItem(hwnd,TB_WTIME) );
          }

          if (blackHuman) {
                // Turn on the 'human' radio button for black player...
                CheckRadioButton ( hwnd, RB_BHUMAN, RB_BCOMPUTER, RB_BHUMAN );

                // Disable think time for Black computer player:
                EnableWindow ( GetDlgItem(hwnd,TB_BTIME), FALSE );
          } else {
                // Turn on the 'computer' radio button for black player...
                CheckRadioButton ( hwnd, RB_BHUMAN, RB_BCOMPUTER, RB_BCOMPUTER );
                SetFocus ( GetDlgItem(hwnd,TB_BTIME) );
          }
      }
      break;

      case WM_COMMAND:
      {
         switch ( wparam )
         {
             case RB_WHUMAN:
                 // Disable white's pipe name box
                 EnableWindow ( GetDlgItem(hwnd,TB_W_PIPE_NAME), FALSE);

                 // Disable the white computer player's think time box
                 EnableWindow ( GetDlgItem(hwnd,TB_WTIME), FALSE );
                 break;

             case RB_WINTERNET:
                 // Disable white's pipe name box
                 EnableWindow ( GetDlgItem(hwnd,TB_W_PIPE_NAME), FALSE);

                 // Disable the white computer player's think time box
                 EnableWindow ( GetDlgItem(hwnd,TB_WTIME), FALSE );
                 break;

             case RB_W_NAMEDPIPE:
                 // Enable white's pipe name box
                 EnableWindow ( GetDlgItem(hwnd,TB_W_PIPE_NAME), TRUE);

                 // Disable the white computer player's think time box
                 EnableWindow ( GetDlgItem(hwnd,TB_WTIME), FALSE );
                 break;

             case RB_WCOMPUTER:
                 // Disable white's pipe name box
                 EnableWindow ( GetDlgItem(hwnd,TB_W_PIPE_NAME), FALSE);

                 // Enable the white computer player's think time box
                 EnableWindow ( GetDlgItem(hwnd,TB_WTIME), TRUE );
                 SetFocus ( GetDlgItem(hwnd,TB_WTIME) );
                 break;

            case RB_BHUMAN:
                // Disable black's pipe name box
                EnableWindow ( GetDlgItem(hwnd,TB_B_PIPE_NAME), FALSE);

                // Disable the black computer player's think time box
                EnableWindow ( GetDlgItem(hwnd,TB_BTIME), FALSE );
                break;

            case RB_BINTERNET:
                // Disable black's pipe name box
                EnableWindow ( GetDlgItem(hwnd,TB_B_PIPE_NAME), FALSE);

                // Disable the black computer player's think time box
                EnableWindow ( GetDlgItem(hwnd,TB_BTIME), FALSE );
                break;

            case RB_B_NAMEDPIPE:
                 // Enable black's pipe name box
                 EnableWindow ( GetDlgItem(hwnd,TB_B_PIPE_NAME), TRUE);

                 // Disable the black computer player's think time box
                 EnableWindow ( GetDlgItem(hwnd,TB_BTIME), FALSE );
                 break;

            case RB_BCOMPUTER:
                // Disable black's pipe name box
                EnableWindow ( GetDlgItem(hwnd,TB_B_PIPE_NAME), FALSE);

                // Enable the black computer player's think time box
                EnableWindow ( GetDlgItem(hwnd,TB_BTIME), TRUE );
                SetFocus ( GetDlgItem(hwnd,TB_BTIME) );
                break;

#if SUPPORT_INTERNET
            case IDC_CHESS_CLIENT:
            {
                BOOL remoteFlag = IsDlgButtonChecked ( hwnd, IDC_CHESS_CLIENT );
                Global_ServerMode = !remoteFlag;

                EnableWindow ( GetDlgItem(hwnd,RB_WHUMAN),      !remoteFlag );
                EnableWindow ( GetDlgItem(hwnd,RB_WCOMPUTER),   !remoteFlag );
                EnableWindow ( GetDlgItem(hwnd,RB_WINTERNET),   !remoteFlag );
                EnableWindow ( GetDlgItem(hwnd,RB_BHUMAN),      !remoteFlag );
                EnableWindow ( GetDlgItem(hwnd,RB_BCOMPUTER),   !remoteFlag );
                EnableWindow ( GetDlgItem(hwnd,RB_BINTERNET),   !remoteFlag );
                EnableWindow ( GetDlgItem(hwnd,RB_W_NAMEDPIPE), !remoteFlag );
                EnableWindow ( GetDlgItem(hwnd,RB_B_NAMEDPIPE), !remoteFlag );

                EnableWindow ( GetDlgItem(hwnd,IDC_REMOTE_IP), remoteFlag );
                if ( remoteFlag )
                    SetFocus ( GetDlgItem(hwnd,IDC_REMOTE_IP) );

                BOOL enableTime = remoteFlag ? FALSE : IsDlgButtonChecked(hwnd,RB_WCOMPUTER);
                EnableWindow ( GetDlgItem(hwnd,TB_WTIME), enableTime );

                enableTime = remoteFlag ? FALSE : IsDlgButtonChecked(hwnd,RB_BCOMPUTER);
                EnableWindow ( GetDlgItem(hwnd,TB_BTIME), enableTime );
            }
            break;
#endif // SUPPORT_INTERNET


            case IDOK:
            {
               result = TRUE;
               SoundEnableFlag = TRUE;

#if SUPPORT_INTERNET
                if ( IsDlgButtonChecked ( hwnd, IDC_CHESS_CLIENT ) )
                {
                    GetWindowText ( GetDlgItem(hwnd,IDC_REMOTE_IP), DefaultServerIpAddress, sizeof(DefaultServerIpAddress) );
                    result = ConnectToChenardServer ( hwnd, DefPlayer, DefaultServerIpAddress );
                    if ( result )
                        EndDialog ( hwnd, IDOK );

                    return TRUE;
                }
#endif


               // Figure out whether white is human or computer
               if ( IsDlgButtonChecked ( hwnd, RB_WHUMAN ) ) {
                   DefPlayer.whiteType = DefPlayerInfo::humanPlayer;
               } else if ( IsDlgButtonChecked ( hwnd, RB_WCOMPUTER ) ) {
                   DefPlayer.whiteType = DefPlayerInfo::computerPlayer;
               } else if (IsDlgButtonChecked (hwnd, RB_W_NAMEDPIPE)) {
                   DefPlayer.whiteType = DefPlayerInfo::namedPipePlayer;
               } else {
                   DefPlayer.whiteType = DefPlayerInfo::internetPlayer;
                   DefPlayer.whiteInternetConnect.waitForClient = 1;
               }

               // Think time for white
               double value = 0;

               if ( !GetWindowText ( GetDlgItem(hwnd,TB_WTIME), WhiteTimeLimit, sizeof(WhiteTimeLimit) ) ||
                    (value = atof(WhiteTimeLimit)) <= 0 )
               {
                   strcpy ( WhiteTimeLimit, defaultTimeLimit );
                   value = atof(defaultTimeLimit);
               }

               if ( value < 0.1 )
                   value = 0.1;

               DefPlayer.whiteThinkTime = long (100 * value);
               DefPlayer.whiteUseSeconds = 1;
               DefPlayer.whiteSearchBias = 1;

               // Figure out whether black is human or computer

               if ( IsDlgButtonChecked ( hwnd, RB_BHUMAN ) ) {
                   DefPlayer.blackType = DefPlayerInfo::humanPlayer;
               } else if ( IsDlgButtonChecked ( hwnd, RB_BCOMPUTER ) ) {
                   DefPlayer.blackType = DefPlayerInfo::computerPlayer;
               } else if (IsDlgButtonChecked (hwnd, RB_B_NAMEDPIPE)) {
                   DefPlayer.blackType = DefPlayerInfo::namedPipePlayer;
               } else {
                   DefPlayer.blackType = DefPlayerInfo::internetPlayer;
                   DefPlayer.blackInternetConnect.waitForClient = 1;
               }

               // Think time for black

               if ( !GetWindowText ( GetDlgItem(hwnd,TB_BTIME), BlackTimeLimit, sizeof(BlackTimeLimit) ) ||
                    (value = atof(BlackTimeLimit)) <= 0 )
               {
                   strcpy ( BlackTimeLimit, defaultTimeLimit );
                   value = atof(defaultTimeLimit);
               }

               if ( value < 0.1 )
                   value = 0.1;

               DefPlayer.blackThinkTime = long(100 * value);
               DefPlayer.blackUseSeconds = 1;
               DefPlayer.blackSearchBias = 1;

               GetWindowText (GetDlgItem(hwnd,TB_W_PIPE_NAME), DefPlayer.whiteServerName, sizeof(DefPlayer.whiteServerName));
               GetWindowText (GetDlgItem(hwnd,TB_B_PIPE_NAME), DefPlayer.blackServerName, sizeof(DefPlayer.blackServerName));

               EndDialog ( hwnd, IDOK );
            }
            break;

            case IDCANCEL:
            {
               EndDialog ( hwnd, IDCANCEL );
            }
            break;
         }
      }
      break;

      default:
         result = FALSE;
         break;
   }

   return result;
}


static bool DefinePlayerDialog ( HWND hwnd )
{
    if ( Global_EnableGA || Global_EnableEndgameDatabaseGen )
        return true;

#if SUPPORT_INTERNET
    int netFlag = 0;
    FILE *netFile = fopen ( "chenard.net", "r" );
    if ( netFile )
    {
        fclose (netFile);
        netFlag = 1;
    }

    LPTSTR restag = netFlag ? 
        MAKEINTRESOURCE(IDD_DEFINE_PLAYERS2) :
        MAKEINTRESOURCE(IDD_DEFINE_PLAYERS);
#else
    LPTSTR restag = MAKEINTRESOURCE(IDD_DEFINE_PLAYERS);
#endif

    INT_PTR fred = DialogBox (
        global_hInstance,
        restag,
        hwnd,
        DLGPROC(DefineChessPlayers_DlgProc) );

#if SUPPORT_INTERNET
    char tempString [256];

    if ( fred == IDOK )
    {
        int iAmServer = Global_ServerMode && 
            (DefPlayer.whiteType == DefPlayerInfo::internetPlayer ||
             DefPlayer.blackType == DefPlayerInfo::internetPlayer);

        if ( iAmServer )
        {
            if ( !InitializeNetwork(hwnd) )
                return false;

            sockaddr_in localAddress;
            memset ( &localAddress, 0, sizeof(localAddress) );
            localAddress.sin_family = AF_INET;
            localAddress.sin_addr.s_addr = INADDR_ANY;
            localAddress.sin_port = htons(InternetChessPlayer::QueryServerPortNumber());

            if ( bind ( Global_MySocket, (sockaddr *)&localAddress, sizeof(localAddress) ) != 0 )
            {
                MessageBox ( hwnd, "Could not bind socket!", "Network Error", MB_OK | MB_ICONERROR );       
                return FALSE;
            }

            ServerConnectThreadId = _beginthread ( ServerConnectThreadFunc, 0, NULL );
            if ( long(ServerConnectThreadId) == -1 )
            {
                MessageBox ( hwnd, "Cannot create server thread!", "Bad Problem!", MB_OK | MB_ICONERROR );
                return false;
            }

            sprintf ( tempString, "Inform opponent(s) that server address is:\n\n%u.%u.%u.%u",
                unsigned ( Global_MyHostEnt.h_addr[0] & 0xff ),
                unsigned ( Global_MyHostEnt.h_addr[1] & 0xff ),
                unsigned ( Global_MyHostEnt.h_addr[2] & 0xff ),
                unsigned ( Global_MyHostEnt.h_addr[3] & 0xff ) );
                
            MessageBox ( hwnd, tempString, "", MB_OK | MB_ICONINFORMATION );
        }
    }
#endif

    return (fred == IDOK) ? true : false;
}


static BOOL SetChessWindowSize ( HWND hwnd, bool showDebugInfo )
{
   RECT rectDesktop;
   GetWindowRect ( GetDesktopWindow(), &rectDesktop );

   int newDX = CHESS_WINDOW_DX;
   int newDY = CHESS_WINDOW_DY;
   int debugDX = 0;

   if ( showDebugInfo )
   {
       debugDX = DEBUG_WINDOW_DX;
       newDX += debugDX;
   }

   RECT newFrameRect;
   newFrameRect.left = (rectDesktop.right - newDX) / 2;
   newFrameRect.right = newFrameRect.left + newDX;
   newFrameRect.top = (rectDesktop.bottom - newDY) / 2;
   newFrameRect.bottom = newFrameRect.top + newDY;

   if ( AdjustWindowRect ( &newFrameRect, global_windowFlags, TRUE ) )
   {
       if ( newFrameRect.left < 0 )
       {
           newFrameRect.right -= newFrameRect.left;
           newFrameRect.left = 0;
       }

       if ( newFrameRect.top < 0 )
       {
           newFrameRect.bottom -= newFrameRect.top;
           newFrameRect.top = 0;
       }

       return SetWindowPos (
           hwnd, HWND_TOP,
           newFrameRect.left,
           newFrameRect.top,
           newFrameRect.right - newFrameRect.left,
           newFrameRect.bottom - newFrameRect.top,
           SWP_SHOWWINDOW );
   }

   return FALSE;   // some error occurred in AdjustWindowRect
}


const char Profile_ViewDebugInfo[]    = "ViewDebugInfo";
const char Profile_PieceStyle[]       = "PieceStyle";
const char Profile_ChessBoardSize[]   = "ChessBoardSize";
const char Profile_WhiteTimeLimit[]   = "WhiteTimeLimit";
const char Profile_BlackTimeLimit[]   = "BlackTimeLimit";
const char Profile_SpeakMovesFlag[]   = "SpeakMovesFlag";
const char Profile_ResignFlag[]       = "ResignFlag";
const char Profile_AnimateMoves[]     = "AnimateMoves";
const char Profile_HiliteMoves[]      = "HiliteMoves";
const char Profile_AutoSingular[]     = "AutoSingular";
const char Profile_ExtendedSearch[]   = "ExtendedSearch";
const char Profile_ServerIpAddress[]  = "ServerIpAddress";
const char Profile_EnableOppTime[]    = "ThinkOnOpponentTime";
const char Profile_AnnounceMate[]     = "AnnounceMate";
const char Profile_SuggestThinkTime[] = "SuggestThinkTime";

const char * const CHENARD_REGISTRY_KEY_NAME = "Chenard";

void ReadChenardSetting (const char *key, const char *defaultValue, char *opt, size_t optSize)
{
    bool readValue = false;     // if remains false, it means we failed to read data and will use defaultValue

    HKEY softwareKeyHandle;
    LSTATUS status = RegOpenKeyEx (
        HKEY_CURRENT_USER,
        "Software",
        0,
        KEY_READ,
        &softwareKeyHandle
    );

    if (status == ERROR_SUCCESS) {
        HKEY chenardKey;

        status = RegOpenKeyEx (
            softwareKeyHandle,
            CHENARD_REGISTRY_KEY_NAME,
            0,
            KEY_READ,
            &chenardKey
        );

        if (status == ERROR_SUCCESS) {
            DWORD dataType;
            DWORD dataSize = (DWORD) optSize;
            status = RegQueryValueEx (chenardKey, key, 0, &dataType, (LPBYTE) opt, &dataSize);
            if (status == ERROR_SUCCESS) {
                if (dataType == REG_SZ) {
                    opt[dataSize] = '\0';   // registry functions aren't guaranteed to null-terminate strings for us
                    readValue = true;       // we have read a valid value; do not fall back on the default value passed in to this function
                }
            }

            RegCloseKey (chenardKey);
        }

        RegCloseKey (softwareKeyHandle);
    }


    if (!readValue) {
        strcpy_s (opt, optSize, defaultValue);
    }
}


LSTATUS WriteChenardSetting (const char *key, const char *value)
{
    HKEY softwareKeyHandle;
    LSTATUS status = RegOpenKeyEx (
        HKEY_CURRENT_USER,
        "Software",
        0,
        KEY_ALL_ACCESS,
        &softwareKeyHandle
    );

    if (status == ERROR_SUCCESS) {
        HKEY chenardKey;

        status = RegCreateKeyEx (
            softwareKeyHandle, 
            CHENARD_REGISTRY_KEY_NAME, 
            0, 
            NULL, 
            0, 
            KEY_ALL_ACCESS, 
            NULL, 
            &chenardKey, 
            NULL
        );

        if (status == ERROR_SUCCESS) {
            status = RegSetValueEx (chenardKey, key, 0, REG_SZ, (const BYTE *)value, (DWORD) (1 + strlen(value)));
            RegCloseKey (chenardKey);
        }

        RegCloseKey (softwareKeyHandle);
    }

    return status;
}


static void LoadChessOptions()
{
    char opt[128];

    ReadChenardSetting ( Profile_ViewDebugInfo, "0", opt, sizeof(opt) );
    Global_AnalysisType = atoi(opt);

    ReadChenardSetting ( Profile_PieceStyle, "2", opt, sizeof(opt) );
    int temp = atoi(opt);
    TheBoardDisplayBuffer.changePieceFont (temp);

    ReadChenardSetting ( Profile_ChessBoardSize, "0", opt, sizeof(opt) );
    temp = atoi(opt);
    NewBoardSize (temp);

    ReadChenardSetting ( Profile_WhiteTimeLimit, "2", WhiteTimeLimit, sizeof(WhiteTimeLimit) );
    ReadChenardSetting ( Profile_BlackTimeLimit, "2", BlackTimeLimit, sizeof(BlackTimeLimit) );

    ReadChenardSetting ( Profile_SpeakMovesFlag, "0", opt, sizeof(opt) );
    Global_SpeakMovesFlag = (atoi(opt) != 0);

    ReadChenardSetting ( Profile_ResignFlag, "0", opt, sizeof(opt) );
    Global_AllowResignFlag = (atoi(opt) != 0);

    ReadChenardSetting ( Profile_AnimateMoves, "1", opt, sizeof(opt) );
    Global_AnimateMoves = (atoi(opt) != 0);

    ReadChenardSetting ( Profile_EnableOppTime, "0", opt, sizeof(opt) );
    Global_EnableOppTime = (atoi(opt) != 0);

    ReadChenardSetting ( Profile_HiliteMoves, "1", opt, sizeof(opt) );
    Global_HiliteMoves = (atoi(opt) != 0);

    ReadChenardSetting ( Profile_AutoSingular, "0", opt, sizeof(opt) );
    Global_AutoSingular = (atoi(opt) != 0);

    ReadChenardSetting ( Profile_ExtendedSearch, "0", opt, sizeof(opt) );
    Global_ExtendedSearch = (atoi(opt) != 0);

    ReadChenardSetting ( Profile_ServerIpAddress, "", DefaultServerIpAddress, sizeof(DefaultServerIpAddress) );

    ReadChenardSetting ( Profile_AnnounceMate, "1", opt, sizeof(opt) );
    Global_EnableMateAnnounce = (atoi(opt) != 0);

    ReadChenardSetting ( Profile_SuggestThinkTime, "500", opt, sizeof(opt) );
    SuggestThinkTime = (INT32) atoi(opt);
}


static void ApplyChessOptions()
{
    // Set the analysis checkmarks
    UINT uCheck = (Global_AnalysisType==0) ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_NONE, uCheck );
    uCheck = (Global_AnalysisType==1) ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_BESTPATH, uCheck );
    uCheck = (Global_AnalysisType==2) ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_CURRENTPATH, uCheck );

    // Set the checkmark for speaking moves...
    uCheck = Global_SpeakMovesFlag ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_VIEW_SPEAKMOVESTHROUGHSOUNDCARD, uCheck );

    // Set the checkmarks for the piece styles
    int pfont = TheBoardDisplayBuffer.queryPieceFont();
    uCheck = (pfont == PIECE_FONT_ORIGINAL) ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_ORIGINAL, uCheck );

    uCheck = (pfont == PIECE_FONT_TILBURG) ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_TILBURG, uCheck );

    uCheck = (pfont == PIECE_FONT_SKAK) ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_SKAK, uCheck );

    // Set the checkmarks for board size
    uCheck = (ChessBoardSize==0) ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_VIEW_SMALLBOARD, uCheck );

    uCheck = (ChessBoardSize==1) ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_VIEW_MEDIUMBOARD, uCheck );

    uCheck = (ChessBoardSize==2) ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_VIEW_LARGEBOARD, uCheck );

    // set checkmark for resign flag
    uCheck = Global_AllowResignFlag ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_GAME_ALLOWCOMPUTERTORESIGN, uCheck );

    uCheck = Global_AnimateMoves ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_ANIMATE, uCheck );

    uCheck = Global_HiliteMoves ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_HIGHLIGHT_MOVE, uCheck );

    uCheck = Global_AutoSingular ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_GAME_AUTO_SINGULAR, uCheck );

    uCheck = Global_ExtendedSearch ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_GAME_ALLOWEXTENDEDSEARCH, uCheck );

    uCheck = Global_EnableOppTime ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem ( HmenuMain, ID_GAME_OPPTIME, uCheck );

    uCheck = Global_EnableMateAnnounce ? MF_CHECKED : MF_UNCHECKED;
    CheckMenuItem (HmenuMain, ID_VIEW_ANNOUNCEMATE, uCheck);
}


static void SaveChessOptions()
{
    char opt [64];

    sprintf ( opt, "%d", Global_AnalysisType );
    WriteChenardSetting (Profile_ViewDebugInfo, opt);

    sprintf ( opt, "%d", TheBoardDisplayBuffer.queryPieceFont() );
    WriteChenardSetting (Profile_PieceStyle, opt);

    sprintf ( opt, "%d", ChessBoardSize );
    WriteChenardSetting (Profile_ChessBoardSize, opt);

    WriteChenardSetting (Profile_WhiteTimeLimit, WhiteTimeLimit);
    WriteChenardSetting (Profile_BlackTimeLimit, BlackTimeLimit);

    sprintf ( opt, "%d", int(Global_SpeakMovesFlag) );
    WriteChenardSetting (Profile_SpeakMovesFlag, opt);

    sprintf ( opt, "%d", int(Global_AllowResignFlag) );
    WriteChenardSetting (Profile_ResignFlag, opt);

    sprintf ( opt, "%d", int(Global_AnimateMoves) );
    WriteChenardSetting (Profile_AnimateMoves, opt);

    sprintf ( opt, "%d", int(Global_HiliteMoves) );
    WriteChenardSetting (Profile_HiliteMoves, opt);

    sprintf ( opt, "%d", int(Global_AutoSingular) );
    WriteChenardSetting (Profile_AutoSingular, opt);

    sprintf ( opt, "%d", int(Global_ExtendedSearch) );
    WriteChenardSetting (Profile_ExtendedSearch, opt);

    sprintf ( opt, "%d", int(Global_EnableOppTime) );
    WriteChenardSetting (Profile_EnableOppTime, opt);

    if (DefaultServerIpAddress[0] != '\0') {
        WriteChenardSetting (Profile_ServerIpAddress, DefaultServerIpAddress);
    }

    sprintf (opt, "%d", int(Global_EnableMateAnnounce));
    WriteChenardSetting (Profile_AnnounceMate, opt);

    sprintf (opt, "%d", int(SuggestThinkTime));
    WriteChenardSetting (Profile_SuggestThinkTime, opt);
}


void ChessThreadFunc ( void * )
{
    // Global_Board.Init();   // no longer reset the board state - might have been loaded from PGN file
    ChessUI_win32_gui  theUserInterface ( HwndMain );
    Global_UI = &theUserInterface;
    TheBoardDisplayBuffer.update ( Global_Board );

    if ( Global_EnableGA )
    {
        // Run the genetic algorithm to evolve eval
        // and move ordering heuristics...

        theUserInterface.allowMateAnnounce ( false );
        Global_AnimateMoves = false;

        ChessGA ga ( theUserInterface, 0 );
        if ( !ga.load() )
        {
            ga.createRandomPopulation ( Global_GA_NumGenes );
            ga.save();
        }

        ga.run();
        return;
    }

    if ( Global_EnableEndgameDatabaseGen )
    {
        // Run the endgame database generator.

        GenerateEndgameDatabases ( Global_Board, theUserInterface );
        return;
    }

    ChessGame theGame ( Global_Board, theUserInterface );
    if ( Global_AutoSingular )
    {
        if ( theUserInterface.queryWhitePlayerType() == DefPlayerInfo::humanPlayer )
        {
            HumanChessPlayer *human = (HumanChessPlayer *) theUserInterface.queryWhitePlayer();
            if ( human )
                human->setAutoSingular (true);
        }

        if ( theUserInterface.queryBlackPlayerType() == DefPlayerInfo::humanPlayer )
        {
            HumanChessPlayer *human = (HumanChessPlayer *) theUserInterface.queryBlackPlayer();
            if ( human )
                human->setAutoSingular (true);
        }
    }

    if ( Global_ExtendedSearch )
    {
        if ( theUserInterface.queryWhitePlayerType() == DefPlayerInfo::computerPlayer )
        {
            ComputerChessPlayer *puter = (ComputerChessPlayer *) theUserInterface.queryWhitePlayer();
            if ( puter )
                puter->setExtendedSearchFlag (true);
        }

        if ( theUserInterface.queryBlackPlayerType() == DefPlayerInfo::computerPlayer )
        {
            ComputerChessPlayer *puter = (ComputerChessPlayer *) theUserInterface.queryBlackPlayer();
            if ( puter )
                puter->setExtendedSearchFlag (true);
        }
    }

    for(;;)
    {
        Global_GameOverFlag = false;
        theGame.Play();
        PostMessage ( HwndMain, UINT(WM_DDC_GAME_OVER), WPARAM(0), LPARAM(0) );

        for(;;)
        {
            Sleep(100);
            int dummySource=0, dummyDest=0;
            if ( ProcessChessCommands ( Global_Board, dummySource, dummyDest ) )
                break;
        }
    }

    globalImmedQuitFlag = true;
    Global_UI = 0;
}

PGN_FILE_STATE LoadGameFromPgnFile (FILE *f, ChessBoard &board);     // loads only the first game in the PGN file


void SetDirectoryFromFilePath (const char *filepath)
{
    const char *backslash = strrchr (filepath, '\\');
    if (backslash) {
        int length = (int) (backslash - filepath);
        char *path = new char [length + 1];
        memcpy (path, filepath, length);
        path[length] = '\0';

        _chdir (path);

        delete[] path;
    }
}


bool LoadGameFile (const char *arg)
{
    if (arg == NULL) {
        return false;   // should never happen, but avoid crash
    }

    const char *firstQuote = strchr (arg, '"');
    const char *secondQuote = firstQuote ? strchr(firstQuote+1,'"') : NULL;
    int firstIndex;
    int secondIndex;
    if (firstQuote && secondQuote) {
        // Assume the filename is the first group of characters that
        // are surrounded by double quote marks...
        firstIndex  = (int) (firstQuote - arg) + 1;     // skip over quote
        secondIndex = (int) (secondQuote - arg);        // point to quote (will be excluded)
    } else {
        // Assume the filename is the first group of non-whitespace characters...
        firstIndex = 0;
        while (arg[firstIndex] && isspace(arg[firstIndex])) {
            ++firstIndex;
        }

        if (arg[firstIndex]) {
            secondIndex = firstIndex + 1;
            while (arg[secondIndex] && !isspace(arg[secondIndex])) {
                ++secondIndex;
            }
        } else {
            secondIndex = firstIndex;   // empty string
        }
    }

    int length = secondIndex - firstIndex;
    if (length <= 0) {
        return false;
    }

    // Extract the filename from the command line.
    // Make a dynamically allocated substring...
    char *filename = new char [1 + length];
    memcpy (filename, arg + firstIndex, length);
    filename[length] = '\0';

    bool loaded = false;

    char msg [256];
    const char *ext = strrchr (filename, '.');
    if (ext) {
        if (0 == stricmp (ext, ".pgn")) {
            // This appears to be a PGN file.
            // Use it to load the game.
            FILE *infile = fopen (filename, "rt");
            if (infile) {
                PGN_FILE_STATE state = LoadGameFromPgnFile (infile, Global_Board);
                if (state == PGN_FILE_STATE_GAMEOVER) {
                    // We successfully loaded the game state into the global chess board.
                    // Now we assist the user by trying to restore the player definitions.
                    // The dialog box will still show, but will be filled in with computer/human
                    // playing White/Black.
                    // If Chenard created the file, it will contain lines like these:
                    // [White "Human Player"]
                    // [Black "Computer Player (Chenard)"]
                    rewind (infile);
                    char line [128];
                    bool foundWhite = false;
                    bool foundBlack = false;
                    while (fgets (line, sizeof(line), infile)) {
                        if (0 == strcmp(line,"[White \"Human Player\"]\n")) {
                            foundWhite = true;
                            PreDefinedWhiteHuman = true;
                        } else if (0 == strcmp(line, "[Black \"Human Player\"]\n")) {
                            foundBlack = true;
                            PreDefinedBlackHuman = true;
                        } else if (0 == strcmp(line, "[White \"Computer Player (Chenard)\"]\n")) {
                            foundWhite = true;
                            PreDefinedWhiteHuman = false;
                        } else if (0 == strcmp(line, "[Black \"Computer Player (Chenard)\"]\n")) {
                            foundBlack = true;
                            PreDefinedBlackHuman = false;
                        }
                    }
                    PreDefinedPlayerSides = (foundWhite && foundBlack);

                    // Remember this game filename so the user can save to it again...
                    strcpy_s (Global_GameFilename, sizeof(Global_GameFilename), filename);
                    Global_HaveFilename = true;
                    loaded = true;

                    // Set the current directory to the location of the executable.
                    // We assume this is where endgame, training, wav, etc files are...
                    SetDirectoryFromFilePath (__argv[0]);
                } else {
                    // Something is wrong with this PGN file.
                    // Let the user know we tried, but could not load the file...
                    const char *error = GetPgnFileStateString (state);
                    sprintf (msg, "Problem loading pgn file:\n%s", error);
                    MessageBox (HwndMain, msg, "PGN Load Error", MB_OK | MB_ICONERROR);

                    // Reset the board...
                    Global_Board.Init();
                }
                fclose (infile);
            } else {
                MessageBox (HwndMain, "Could not open the specified PGN file.", "PGN Load Error", MB_OK | MB_ICONERROR);
            }
        }
    }

    delete[] filename;
    return loaded;
}


int WINAPI WinMain(
    HINSTANCE   hInstance,          // handle to current instance
    HINSTANCE   hPrevInstance,      // handle to previous instance
    LPSTR       lpCmdLine,          // pointer to command line
    int         nCmdShow )          // show state of window
{
    global_hInstance = hInstance;
    LoadChessOptions();

    extern CRITICAL_SECTION SuggestMoveCriticalSection;
    InitializeCriticalSection (&SuggestMoveCriticalSection);

    static CHAR chessWindowClass[] = "DDC_Chenard";
    WNDCLASS wndclass;
    if ( !hPrevInstance )
    {
        wndclass.style       =  CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc =  ChessWndProc;
        wndclass.cbClsExtra  =  0;
        wndclass.cbWndExtra  =  0;
        wndclass.hInstance   =  hInstance;
        wndclass.hIcon       =  LoadIcon ( NULL, IDI_APPLICATION );
        wndclass.hCursor     =  LoadCursor ( NULL, IDC_ARROW );
        wndclass.hbrBackground   =  (HBRUSH) GetStockObject ( WHITE_BRUSH );
        wndclass.lpszMenuName    =  NULL;
        wndclass.lpszClassName   =  chessWindowClass;
        RegisterClass ( &wndclass );
    }

    if (lpCmdLine[0] != '\0') {
        if (LoadGameFile (lpCmdLine)) {
            // No need to examine the command line further: we have already loaded the game.
        } else if ( lpCmdLine[0] == '-' ) {
            if ( lpCmdLine[1] == 'g' ) {
                Global_EnableGA = true;
                if ( lpCmdLine[2] ) {
                    Global_GA_NumGenes = atoi(&lpCmdLine[2]);
                }
            } else if ( lpCmdLine[1]=='d' && lpCmdLine[2]=='b' ) {    // -db : generate endgame database
                Global_EnableEndgameDatabaseGen = true;
                Global_EnableBoardBreathe = false;

                switch (lpCmdLine[3]) {
                    case 'd':   // -dbd : dump info about existing databases
                        Global_EndgameDatabaseMode = 1;
                        break;

                    case 's':   // -dbs : analyze symmetries of databases
                        Global_EndgameDatabaseMode = 2;
                        break;

                    default:
                        Global_EndgameDatabaseMode = 0;     // -db : actually generate the databases
                        break;
                }
            }
        }
    }

    HmenuMain = LoadMenu ( hInstance, MAKEINTRESOURCE(999) );

    HwndMain = CreateWindow (
        chessWindowClass,
        TitleBarText_WhitesTurn,
        global_windowFlags,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        HmenuMain,
        hInstance,
        NULL 
    );

    ApplyChessOptions();
    SetChessWindowSize ( HwndMain, (Global_AnalysisType != 0) );

    HACCEL hAccel = LoadAccelerators ( hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1) );

    MSG msg;
    msg.wParam = 0;

    int DisplayTimerEnabled = (SetTimer(HwndMain,2,500,NULL) != NULL);

    uintptr_t chessThreadID = _beginthread ( ChessThreadFunc, 0, NULL );
    if ( chessThreadID != (unsigned long)(-1) )
    {
        while ( GetMessage ( &msg, NULL, 0, 0 ) && !Global_AbortFlag )
        {
            if ( !TranslateAccelerator(HwndMain,hAccel,&msg) )
            {
                TranslateMessage (&msg);
                DispatchMessage (&msg);
            }
        }
    }
    else
    {
        // Put error code here!
        ChessBeep ( 880, 200 );
    }

    if ( DisplayTimerEnabled )
        KillTimer ( HwndMain, 2 );

    SaveChessOptions();

    ChessDisplayTextBuffer::DeleteAll();
    ExitProcess(0);   // This is needed to make sure the chess thread is killed
    return 0;
}


static void CheckHumanMoveState ( bool insideHumanMove )
{
    UINT uEnableHuman  = insideHumanMove ? (MF_BYCOMMAND | MF_ENABLED) : (MF_BYCOMMAND | MF_GRAYED);

    // Things to enable only when it is a human player's move...
    EnableMenuItem ( HmenuMain, ID_CHENARD_FILE_NEW, uEnableHuman );
    EnableMenuItem ( HmenuMain, ID_CHENARD_FILE_OPEN, uEnableHuman );
    EnableMenuItem ( HmenuMain, ID_CHENARD_FILE_SAVE, uEnableHuman );
    EnableMenuItem ( HmenuMain, ID_CHENARD_FILE_SAVEAS, uEnableHuman );
    EnableMenuItem ( HmenuMain, ID_EDIT_CLEARBOARD, uEnableHuman );
    EnableMenuItem ( HmenuMain, ID_EDIT_EDITMODE, uEnableHuman );
    EnableMenuItem ( HmenuMain, ID_EDIT_UNDOMOVE, uEnableHuman );
    EnableMenuItem ( HmenuMain, ID_EDIT_REDOMOVE, uEnableHuman );
    EnableMenuItem ( HmenuMain, ID_GAME_SUGGEST, uEnableHuman );
    EnableMenuItem ( HmenuMain, ID_ENTER_FEN, uEnableHuman );

    // Things to enable only when it is a computer player's move...
    // Allow user to abort computer thinking when it is the computer's turn, or when the suggest-move thinker is active...
    extern ComputerChessPlayer *Global_SuggestMoveThinker;
    UINT uEnableComputer = (insideHumanMove && (Global_SuggestMoveThinker == NULL)) ? (MF_BYCOMMAND | MF_GRAYED) : (MF_BYCOMMAND | MF_ENABLED);
    EnableMenuItem ( HmenuMain, ID_GAME_FORCEMOVE, uEnableComputer );
}


static void SendEditRequest ( int x, int y, SQUARE s )
{
    // If the following flag is set, it means that an edit
    // is still pending.  We don't want to get confused, so
    // we ignore mouse clicks until then.

    if ( Global_BoardEditRequest.getState() == BER_NOTHING )
    {
        Global_BoardEditRequest.sendSingleSquareEdit (x, y, s);
    }
}


static bool CopyForsythEdwardsNotation()
{
    const char *copyError = "Copy Forsyth-Edwards Notation Error";

    if (!OpenClipboard(HwndMain)) {
        MessageBox (
            HwndMain,
            "Cannot open the Windows clipboard!",
            copyError,
            MB_ICONERROR | MB_OK 
        );
        return false;
    }

    const unsigned bufSize = 256;
    char buffer [bufSize];

    if (!Global_Board.GetForsythEdwardsNotation (buffer, bufSize)) {
        MessageBox (
            HwndMain,
            "Error getting FEN string from board!",
            copyError,
            MB_ICONERROR | MB_OK 
        );
        return false;
    }

    bool result = true;
    HGLOBAL hGlobalMemory = GlobalAlloc ( GHND, strlen(buffer)+1 );
    if (hGlobalMemory) {
        char far *globalPtr = (char far *) GlobalLock (hGlobalMemory);
        if (globalPtr) {
            char far *a = globalPtr;
            const char far *b = buffer;
            do { *a++ = *b; } while ( *b++ );
            GlobalUnlock (hGlobalMemory);
            EmptyClipboard();
            SetClipboardData ( CF_TEXT, hGlobalMemory );
        } else {
            GlobalDiscard ( hGlobalMemory );
            MessageBox (
                HwndMain,
                "Error locking global memory handle!",
                copyError,
                MB_ICONERROR | MB_OK 
            );

            result = false;
        }
    } else {
        MessageBox (
            HwndMain,
            "Error allocating global memory handle!",
            copyError,
            MB_ICONERROR | MB_OK 
        );

        result = false;
    }

    CloseClipboard();
    return result;
}


static bool CopyGameListing()
{
    const char *copyError = "Copy Game Listing Error";
    if ( Global_Board.HasBeenEdited() )
    {
        MessageBox (
            HwndMain,
            "Cannot get game listing because board has been edited",
            copyError,
            MB_ICONERROR | MB_OK );
        return false;
    }

    if ( !OpenClipboard(HwndMain) )
    {
        MessageBox (
            HwndMain,
            "Cannot open the Windows clipboard!",
            copyError,
            MB_ICONERROR | MB_OK );
        return false;
    }

    const unsigned bufSize = 64 * 1024;
    char *buffer = new char [bufSize];
    if ( !buffer )
    {
        MessageBox (
            HwndMain,
            "Out of memory allocating buffer for game listing!",
            copyError,
            MB_ICONERROR | MB_OK );

        CloseClipboard();
        return false;
    }

    GetGameListing ( Global_Board, buffer, bufSize );

    bool result = true;
    HGLOBAL hGlobalMemory = GlobalAlloc ( GHND, strlen(buffer)+1 );
    if ( hGlobalMemory )
    {
        char far *globalPtr = (char far *) GlobalLock (hGlobalMemory);
        if ( globalPtr )
        {
            char far *a = globalPtr;
            const char far *b = buffer;
            do { *a++ = *b; } while ( *b++ );
            GlobalUnlock (hGlobalMemory);
            EmptyClipboard();
            SetClipboardData ( CF_TEXT, hGlobalMemory );
        }
        else
        {
            GlobalDiscard ( hGlobalMemory );
            MessageBox (
                HwndMain,
                "Error locking global memory handle!",
                copyError,
                MB_ICONERROR | MB_OK );

            result = false;
        }
    }
    else
    {
        MessageBox (
            HwndMain,
            "Error allocating global memory handle!",
            copyError,
            MB_ICONERROR | MB_OK );

        result = false;
    }

    CloseClipboard();
    delete[] buffer;
    return result;
}

void Chenard_FileSaveAs()
{
    static char filenameBuffer [256];
    strcpy ( filenameBuffer, "*.pgn" );

    OPENFILENAME ofn;
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = HwndMain;
    ofn.hInstance = NULL;
    ofn.lpstrFilter = "Portable Game Notation (*.pgn)\0.pgn\0Chenard Chess Games (*.gam)\0.gam\0";
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = filenameBuffer;
    ofn.nMaxFile = sizeof(filenameBuffer);
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = "Save Game As";
    ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = "pgn";
    ofn.lCustData = 0;
    ofn.lpfnHook = NULL;
    ofn.lpTemplateName = NULL;

    if ( GetSaveFileName(&ofn) )
    {
        strcpy ( Global_GameFilename, filenameBuffer );
        Global_GameSaveFlag = true;
    }
}


void Chenard_FileSave()
{
    if ( Global_HaveFilename )
        Global_GameSaveFlag = true;
    else
        Chenard_FileSaveAs();
}


void Chenard_FileOpen()
{
    static char filenameBuffer [256];
    strcpy ( filenameBuffer, "*.pgn;*.gam" );

    OPENFILENAME ofn;
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = HwndMain;
    ofn.hInstance = NULL;
    ofn.lpstrFilter = "Chess Games\0*.pgn;*.gam\0";
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter = 0;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = filenameBuffer;
    ofn.nMaxFile = sizeof(filenameBuffer);
    ofn.lpstrFileTitle = NULL;
    ofn.nMaxFileTitle = 0;
    ofn.lpstrInitialDir = NULL;
    ofn.lpstrTitle = "Open Game";
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
    ofn.nFileOffset = 0;
    ofn.nFileExtension = 0;
    ofn.lpstrDefExt = "pgn";
    ofn.lCustData = 0;
    ofn.lpfnHook = NULL;
    ofn.lpTemplateName = NULL;

    if ( GetOpenFileName(&ofn) )
    {
        strcpy ( Global_GameFilename, filenameBuffer );
        Global_GameOpenFlag = true;
    }
}


static bool WaveFilesExist()
{
	char c;
    char filename [256];

    // See if 1.WAV through 8.WAV exist...

    for ( c='1'; c<='8'; c++ )
    {
        sprintf ( filename, "%c.wav", c );
        if ( _access ( filename, 4 ) != 0 )  return false;
    }

    // See if A.WAV through H.WAV exist...
    for ( c='a'; c<='h'; c++ )
    {
        sprintf ( filename, "%c.wav", c );
        if ( _access ( filename, 4 ) != 0 )  return false;
    }

    // Check on the WAV files with the piece names

    if ( _access ( "knight.wav", 4 ) != 0 )     return false;
    if ( _access ( "bishop.wav", 4 ) != 0 )     return false;
    if ( _access ( "rook.wav", 4 ) != 0 )       return false;
    if ( _access ( "queen.wav", 4 ) != 0 )      return false;
    if ( _access ( "king.wav", 4 ) != 0 )       return false;

    // Other WAV files...

    if ( _access ( "check.wav", 4 ) != 0 )      return false;
    if ( _access ( "ep.wav", 4 ) != 0 )         return false;
    if ( _access ( "mate.wav", 4 ) != 0 )       return false;
    if ( _access ( "movesto.wav", 4 ) != 0 )    return false;
    if ( _access ( "oo.wav", 4 ) != 0 )         return false;
    if ( _access ( "ooo.wav", 4 ) != 0 )        return false;
    if ( _access ( "prom.wav", 4 ) != 0 )       return false;
    if ( _access ( "stale.wav", 4 ) != 0 )      return false;
    if ( _access ( "takes.wav", 4 ) != 0 )      return false;

    return true;
}


static void UpdateAnalysisDisplay ()
{
    // update nodes evaluated...

    extern ComputerChessPlayer *ComputerPlayerSearching;
    ComputerChessPlayer *cp = ComputerPlayerSearching;
    if ( Global_UI && cp )
    {
        extern INT32 TimeSearchStarted;

        INT32 currentTime = ChessTime();
        Global_UI->ReportComputerStats (
            currentTime - TimeSearchStarted,
            0,
            cp->queryNodesEvaluated(),
            0, 0, 0, 0 );
    }
}


LRESULT CALLBACK ChessWndProc (
    HWND hwnd,
    UINT msg,
    WPARAM wparam,
    LPARAM lparam )
{
    BOOL result = FALSE;
    char buffer [128];
    static bool insideHumanMove = false;

    switch ( msg )
    {
        case WM_INITMENU:
            CheckHumanMoveState ( insideHumanMove );
            break;

        case WM_TIMER:
        {
#ifdef CHENARD_PROFILER
            if ( wparam == 1 )   // make sure it's really our timer
                ++ProfilerHitCount[ProfilerIndex];
#endif
            if ( wparam == 2 )   // analysis display update timer
                UpdateAnalysisDisplay ();
        }
        break;

        case WM_CLOSE:
        {
    #if 0
            if ( Global_GameModifiedFlag )
            {
                int choice = MessageBox (
                    hwnd,
                    "Changes to game will be lost - do you really want to quit?",
                    ChessProgramName,
                    MB_ICONQUESTION | MB_YESNO );

                if ( choice == IDNO )
                    break;   // Don't exit the program after all.
            }
    #endif

#if SUPPORT_INTERNET
            if ( Global_NetworkInit )
            {
                WSACleanup();
                Global_NetworkInit = false;
            }
#endif

            PostMessage ( hwnd, WM_QUIT, 0, 0 );
        }
        break;

        case WM_CREATE:
        {
            if ( !DefinePlayerDialog(hwnd) )
            {
                Global_AbortFlag = true;
            }
            else
            {
                const int textSep = 2;
                const int textHeight = 12;
                const int textWidth = 8;

                ChessDisplayTextBuffer *tb = new ChessDisplayTextBuffer (
                    hwnd, STATIC_ID_WHITES_MOVE, CHESS_BOARD_BORDER_DX, 0,
                    ChessDisplayTextBuffer::margin_bottom, ANSI_VAR_FONT );

                tb = new ChessDisplayTextBuffer (
                    hwnd, STATIC_ID_BLACKS_MOVE, 170, 0,
                    ChessDisplayTextBuffer::margin_bottom, ANSI_VAR_FONT );

                tb = new ChessDisplayTextBuffer (
                    hwnd, STATIC_ID_THINKING, SQUARE_SCREENX1(8) - 8*textWidth, 0,
                    ChessDisplayTextBuffer::margin_bottom, ANSI_VAR_FONT );

                int textY = 8;
                int textID;
                int depth = 0;
                for ( textID = FIRST_RIGHT_TEXTID;
                   textID <= LAST_RIGHT_TEXTID;
                   textID++, depth++ )
                {
                    // Creating the object puts it in the class's list of instances
                    tb = new ChessDisplayTextBuffer (
                        hwnd, textID, 0, textY,
                        ChessDisplayTextBuffer::margin_right, ANSI_FIXED_FONT,
                        (textID>=STATIC_ID_BESTPATH(0)) ? (depth & 1) : 0 );

                    textY += (textHeight + textSep);
                }

                for ( textID = STATIC_ID_ADHOC1; textID <= STATIC_ID_ADHOC2; ++textID )
                {
                    tb = new ChessDisplayTextBuffer (
                        hwnd, textID, 0, textY,
                        ChessDisplayTextBuffer::margin_right, ANSI_FIXED_FONT, 0 );

                    textY += (textHeight + textSep);
                }

                char coordString [2] = {0, 0};
                for ( int coord = 0; coord < 8; ++coord )
                {
                    tb = new ChessDisplayTextBuffer (
                        hwnd, STATIC_ID_COORD_RANK_BASE + coord,
                        0,  // x-coord will be calculated later by RepositionAll()
                        0,  // same for y-coord
                        ChessDisplayTextBuffer::margin_rank,
                        ANSI_VAR_FONT, 1 );

                    coordString[0] = '1' + coord;
                    tb->setText ( coordString );

                    tb = new ChessDisplayTextBuffer (
                        hwnd, STATIC_ID_COORD_FILE_BASE + coord,
                        0,  // x-coord will be calculated later by RepositionAll()
                        0,  // same for y-coord
                        ChessDisplayTextBuffer::margin_file,
                        ANSI_VAR_FONT, 1 );

                    coordString[0] = 'a' + coord;
                    tb->setText ( coordString );
                }

                ChessDisplayTextBuffer::RepositionAll();
            }
        }
        break;

        case WM_COMMAND:   // Mostly handles program's menus
        {
            switch ( LOWORD(wparam) )
            {
                case ID_CHENARD_FILE_NEW:
                    Global_ResetGameFlag = true;
                    break;

                case ID_CHENARD_FILE_OPEN:
                    Chenard_FileOpen();
                    break;

                case ID_CHENARD_FILE_SAVE:
                    Chenard_FileSave();
                    break;

                case ID_CHENARD_FILE_SAVEAS:
                    Chenard_FileSaveAs();
                    break;

                case ID_CHENARD_FILE_EXIT:
                    SendMessage ( hwnd, WM_CLOSE, 0, 0 );
                    break;

                case ID_EDIT_CLEARBOARD:
                    Global_ClearBoardFlag = true;
                    break;

                case ID_EDIT_REDOMOVE:
                    Global_RedoMoveFlag = true;
                    break;

                case ID_EDIT_UNDOMOVE:
                    Global_UndoMoveFlag = true;
                    break;

                case ID_EDIT_EDITMODE:
                {
                    int editMode = GetMenuState ( HmenuMain, ID_EDIT_EDITMODE, MF_BYCOMMAND );
                    editMode = (editMode ^ MF_CHECKED) & MF_CHECKED;
                    CheckMenuItem ( HmenuMain, ID_EDIT_EDITMODE, editMode | MF_BYCOMMAND );
                }
                break;

                case ID_EDIT_COPYGAMELISTING:
                {
                    if ( CopyGameListing() )
                    {
                        MessageBox ( 
                            hwnd,
                            "A text listing of the game has been copied to the clipboard.\n"
                            "You can now paste the listing into a text editor, word processor, etc.",
                            CHESS_PROGRAM_NAME,
                            MB_ICONINFORMATION | MB_OK );
                    }
                }
                break;

                case ID_HELP_VISITCHENARDWEBPAGE:
                {
                    ShellExecute (NULL, "open", "http://cosinekitty.com/chenard/", NULL, NULL, SW_SHOWNORMAL);
                }
                break;

                case ID_EDIT_COPY_FEN:
                {
                    if (CopyForsythEdwardsNotation()) {
                        MessageBox (
                            hwnd,
                            "The Forsyth-Edwards Notation (FEN) for the current "
                            "board position has been copied to the clipboard.",
                            CHESS_PROGRAM_NAME,
                            MB_ICONINFORMATION | MB_OK 
                        );
                    }
                }
                break;

                case ID_VIEW_ROTATEBOARD:
                    TheBoardDisplayBuffer.toggleView();
                    TheBoardDisplayBuffer.updateAlgebraicCoords();
                    TheBoardDisplayBuffer.freshenBoard();
                    break;

                case ID_VIEW_FRESHEN:
                    TheBoardDisplayBuffer.freshenBoard();
                    break;

                case ID_VIEW_PIECESTYLE_ORIGINAL:
                {
                    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_ORIGINAL, MF_CHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_TILBURG,  MF_UNCHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_SKAK,     MF_UNCHECKED );
                    if ( TheBoardDisplayBuffer.queryPieceFont() != PIECE_FONT_ORIGINAL )
                    {
                        TheBoardDisplayBuffer.changePieceFont (PIECE_FONT_ORIGINAL);
                        ChessDisplayTextBuffer::RepositionAll();
                        SetChessWindowSize ( hwnd, (Global_AnalysisType != 0) );
                        TheBoardDisplayBuffer.freshenBoard();
                    }
                }
                break;

                case ID_DUMP_XPOS_TABLE:
                {
                    const char *dumpFilename = "xposdump.txt";
                    ComputerChessPlayer::XposTable->debugDump ( dumpFilename );
                    sprintf ( buffer, 
                        "Transposition table has been dumped to file '%s'",
                        dumpFilename );

                    MessageBox ( hwnd, buffer, CHESS_PROGRAM_NAME, MB_ICONINFORMATION | MB_OK );
                }
                break;

                case ID_VIEW_PIECESTYLE_TILBURG:
                {
                    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_ORIGINAL, MF_UNCHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_TILBURG,  MF_CHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_SKAK,     MF_UNCHECKED );
                    if ( TheBoardDisplayBuffer.queryPieceFont() != PIECE_FONT_TILBURG )
                    {
                        TheBoardDisplayBuffer.changePieceFont (PIECE_FONT_TILBURG);
                        ChessDisplayTextBuffer::RepositionAll();
                        SetChessWindowSize ( hwnd, (Global_AnalysisType != 0) );
                        TheBoardDisplayBuffer.freshenBoard();
                    }
                }
                break;

                case ID_VIEW_PIECESTYLE_SKAK:
                {
                    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_ORIGINAL, MF_UNCHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_TILBURG,  MF_UNCHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_PIECESTYLE_SKAK,     MF_CHECKED );
                    if ( TheBoardDisplayBuffer.queryPieceFont() != PIECE_FONT_SKAK )
                    {
                        TheBoardDisplayBuffer.changePieceFont (PIECE_FONT_SKAK);
                        ChessDisplayTextBuffer::RepositionAll();
                        SetChessWindowSize ( hwnd, (Global_AnalysisType != 0) );
                        TheBoardDisplayBuffer.freshenBoard();
                    }
                }
                break;

                case ID_VIEW_SMALLBOARD:
                {
                    CheckMenuItem ( HmenuMain, ID_VIEW_SMALLBOARD,  MF_CHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_MEDIUMBOARD, MF_UNCHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_LARGEBOARD,  MF_UNCHECKED );
                    NewBoardSize ( 0 );
                    ChessDisplayTextBuffer::RepositionAll();
                    SetChessWindowSize ( hwnd, (Global_AnalysisType != 0) );
                }
                break;

                case ID_VIEW_MEDIUMBOARD:
                {
                    CheckMenuItem ( HmenuMain, ID_VIEW_SMALLBOARD,  MF_UNCHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_MEDIUMBOARD, MF_CHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_LARGEBOARD,  MF_UNCHECKED );
                    NewBoardSize ( 1 );
                    ChessDisplayTextBuffer::RepositionAll();
                    SetChessWindowSize ( hwnd, (Global_AnalysisType != 0) );
                }
                break;

                case ID_VIEW_LARGEBOARD:
                {
                    CheckMenuItem ( HmenuMain, ID_VIEW_SMALLBOARD,  MF_UNCHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_MEDIUMBOARD, MF_UNCHECKED );
                    CheckMenuItem ( HmenuMain, ID_VIEW_LARGEBOARD,  MF_CHECKED );
                    NewBoardSize ( 2 );
                    ChessDisplayTextBuffer::RepositionAll();
                    SetChessWindowSize ( hwnd, (Global_AnalysisType != 0) );
                }
                break;

                case ID_VIEW_ANALYSIS_NONE:
                {
                    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_NONE, MF_CHECKED | MF_BYCOMMAND );
                    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_BESTPATH, MF_UNCHECKED | MF_BYCOMMAND );
                    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_CURRENTPATH, MF_UNCHECKED | MF_BYCOMMAND );
                    Global_AnalysisType = 0;
                    ChessDisplayTextBuffer::RepositionAll();
                    SetChessWindowSize ( hwnd, (Global_AnalysisType != 0) );
                }
                break;

                case ID_VIEW_ANALYSIS_BESTPATH:
                {
                    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_NONE, MF_UNCHECKED | MF_BYCOMMAND );
                    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_BESTPATH, MF_CHECKED | MF_BYCOMMAND );
                    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_CURRENTPATH, MF_UNCHECKED | MF_BYCOMMAND );
                    Global_AnalysisType = 1;
                    ChessDisplayTextBuffer::RepositionAll();
                    SetChessWindowSize ( hwnd, (Global_AnalysisType != 0) );
                }
                break;

                case ID_VIEW_ANALYSIS_CURRENTPATH:
                {
                    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_NONE, MF_UNCHECKED | MF_BYCOMMAND );
                    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_BESTPATH, MF_UNCHECKED | MF_BYCOMMAND );
                    CheckMenuItem ( HmenuMain, ID_VIEW_ANALYSIS_CURRENTPATH, MF_CHECKED | MF_BYCOMMAND );
                    Global_AnalysisType = 2;
                    ChessDisplayTextBuffer::RepositionAll();
                    SetChessWindowSize ( hwnd, (Global_AnalysisType != 0) );
                }
                break;

                case ID_ANIMATE:
                {
                    int animateFlag = GetMenuState ( HmenuMain, ID_ANIMATE, MF_BYCOMMAND );
                    animateFlag = (animateFlag ^ MF_CHECKED) & MF_CHECKED;
                    Global_AnimateMoves = animateFlag ? true : false;
                    CheckMenuItem ( HmenuMain, ID_ANIMATE, animateFlag | MF_BYCOMMAND );
                }
                break;

                case ID_GAME_OPPTIME:
                {
                    int oppTimeFlag = GetMenuState ( HmenuMain, ID_GAME_OPPTIME, MF_BYCOMMAND );
                    oppTimeFlag = (oppTimeFlag ^ MF_CHECKED) & MF_CHECKED;
                    Global_EnableOppTime = oppTimeFlag ? true : false;
                    CheckMenuItem ( HmenuMain, ID_GAME_OPPTIME, oppTimeFlag | MF_BYCOMMAND );
                }
                break;

                case ID_HIGHLIGHT_MOVE:
                {
                    int hiliteFlag = GetMenuState ( HmenuMain, ID_HIGHLIGHT_MOVE, MF_BYCOMMAND );
                    hiliteFlag = (hiliteFlag ^ MF_CHECKED) & MF_CHECKED;
                    Global_HiliteMoves = hiliteFlag ? true : false;
                    CheckMenuItem ( HmenuMain, ID_HIGHLIGHT_MOVE, hiliteFlag | MF_BYCOMMAND );
                }
                break;

                case ID_VIEW_SPEAKMOVESTHROUGHSOUNDCARD:
                {
                    int speak = GetMenuState ( HmenuMain, ID_VIEW_SPEAKMOVESTHROUGHSOUNDCARD, MF_BYCOMMAND );
                    speak = (speak ^ MF_CHECKED) & MF_CHECKED;
                    if ( speak )
                    {
                        if ( WaveFilesExist() )
                        {
                            Global_SpeakMovesFlag = true;
                        }
                        else
                        {
                            Global_SpeakMovesFlag = false;
                            speak = 0;
                        
                            MessageBox (
                                HwndMain,
"Chenard is unable to speak because "
"one or more WAV files (audio recordings) are missing. "
"Note that these files are distributed separately from "
"this program due to their size and the fact that some people "
"will not want to use this feature.\n\n"
"Under the Help menu, choose \"Visit Chenard web page\", "
"download chenwav.zip, and unzip its contents into the same directory "
"where you saved this program (winchen.exe or winchen64.exe)."
,
                            "Chenard: Cannot speak!",   // address of title of message box  
                            MB_ICONERROR | MB_OK );
                        }
                    }
                    else
                    {
                        Global_SpeakMovesFlag = false;
                    }
                    CheckMenuItem ( HmenuMain, ID_VIEW_SPEAKMOVESTHROUGHSOUNDCARD, speak | MF_BYCOMMAND );
                }
                break;

                case ID_VIEW_ANNOUNCEMATE:
                {
                    int announce = GetMenuState (HmenuMain, ID_VIEW_ANNOUNCEMATE, MF_BYCOMMAND);
                    announce = (announce ^ MF_CHECKED) & MF_CHECKED;    // toggle
                    Global_EnableMateAnnounce = (announce != 0);
                    if (Global_UI) {
                        Global_UI->allowMateAnnounce (Global_EnableMateAnnounce);
                    }
                    CheckMenuItem (HmenuMain, ID_VIEW_ANNOUNCEMATE, announce | MF_BYCOMMAND);
                }
                break;
    
                case ID_GAME_FORCEMOVE:
                {
                    if ( Global_UI )
                        Global_UI->forceMove();
                }
                break;

                case ID_GAME_SUGGEST:
                {
                    extern bool Global_SuggestMoveFlag;
                    Global_SuggestMoveFlag = true;
                }
                break;

                case ID_GAME_TACTICALBENCHMARK:
                {
                    Global_TacticalBenchmarkFlag = true;
                }
                break;

                case ID_GAME_ALLOWEXTENDEDSEARCH:
                {
                    if ( Global_UI )
                    {
                        int flag = GetMenuState ( HmenuMain, ID_GAME_ALLOWEXTENDEDSEARCH, MF_BYCOMMAND );
                        flag = (flag ^ MF_CHECKED) & MF_CHECKED;
                        Global_ExtendedSearch = flag ? true : false;
                        CheckMenuItem ( HmenuMain, ID_GAME_ALLOWEXTENDEDSEARCH, flag | MF_BYCOMMAND );
                        MessageBox ( HwndMain,
                            Global_ExtendedSearch ?
                            "The computer will now think longer when it thinks it was about to make a mistake." :
                            "The computer will now use no more than its allowed think time.",
                            CHESS_PROGRAM_NAME,
                            MB_OK | MB_ICONINFORMATION );

                        if ( Global_UI->queryWhitePlayerType() == DefPlayerInfo::computerPlayer )
                        {
                            ComputerChessPlayer *puter =
                                (ComputerChessPlayer *)Global_UI->queryWhitePlayer();

                            if ( puter )
                                puter->setExtendedSearchFlag ( Global_ExtendedSearch );
                        }

                        if ( Global_UI->queryBlackPlayerType() == DefPlayerInfo::computerPlayer )
                        {
                            ComputerChessPlayer *puter =
                                (ComputerChessPlayer *)Global_UI->queryBlackPlayer();

                            if ( puter )
                                puter->setExtendedSearchFlag ( Global_ExtendedSearch );
                        }
                    }
                }
                break;

                case ID_GAME_AUTO_SINGULAR:
                {
                    if ( Global_UI )
                    {
                        int flag = GetMenuState ( HmenuMain, ID_GAME_AUTO_SINGULAR, MF_BYCOMMAND );
                        flag = (flag ^ MF_CHECKED) & MF_CHECKED;
                        Global_AutoSingular = flag ? true : false;
                        CheckMenuItem ( HmenuMain, ID_GAME_AUTO_SINGULAR, flag | MF_BYCOMMAND );
                        MessageBox ( HwndMain, 
                            Global_AutoSingular ?
                            "When you have only one legal move, it will be made for you." :
                            "When you have only one legal move, you must manually make it.",
                            CHESS_PROGRAM_NAME,
                            MB_OK | MB_ICONINFORMATION );

                        if ( Global_UI->queryWhitePlayerType() == DefPlayerInfo::humanPlayer )
                        {
                            HumanChessPlayer *human = 
                                (HumanChessPlayer *)Global_UI->queryWhitePlayer();

                            if ( human )
                                human->setAutoSingular ( Global_AutoSingular );
                        }

                        if ( Global_UI->queryBlackPlayerType() == DefPlayerInfo::humanPlayer )
                        {
                            HumanChessPlayer *human = 
                                (HumanChessPlayer *)Global_UI->queryBlackPlayer();

                            if ( human )
                                human->setAutoSingular ( Global_AutoSingular );
                        }
                    }
                }
                break;

                case ID_GAME_ALLOWCOMPUTERTORESIGN:
                {
                    int flag = GetMenuState ( HmenuMain, ID_GAME_ALLOWCOMPUTERTORESIGN, MF_BYCOMMAND );
                    flag = (flag ^ MF_CHECKED) & MF_CHECKED;
                    Global_AllowResignFlag = flag ? true : false;
                    CheckMenuItem ( HmenuMain, ID_GAME_ALLOWCOMPUTERTORESIGN, flag | MF_BYCOMMAND );
                }
                break;

                case ID_GAME_RESIGN:
                {
                    if ( MessageBox ( 
                        HwndMain, 
                        "Do you really want to resign?", 
                        CHESS_PROGRAM_NAME,
                        MB_ICONQUESTION | MB_YESNO ) == IDYES )
                    {
                        extern bool Global_UserResign;
                        Global_UserResign = true;
                    }
                }
                break;

                case ID_EDIT_THINK_TIMES:
                {
                    if ( Global_UI )
                    {
                        DialogBox (
                            global_hInstance,
                            MAKEINTRESOURCE(IDD_EDIT_THINK_TIMES),
                            hwnd,
                            DLGPROC(EditThinkTimes_DlgProc) );
                        break;
                    }
                }
                break;

                case ID_ENTER_FEN:
                    DialogBox (
                        global_hInstance,
                        MAKEINTRESOURCE(IDD_ENTER_FEN),
                        hwnd,
                        DLGPROC(EnterFen_DlgProc)
                    );
                    break;

                case ID_HELP_ABOUT:
                    DialogBox (
                        global_hInstance,
                        MAKEINTRESOURCE(IDD_ABOUT),
                        hwnd,
                        DLGPROC(About_DlgProc) );
                    break;

                case ID_HELP_HOWDOICASTLE:
                    MessageBox ( 
                        HwndMain,
                        "To castle, click on your king, then click the square "
                        "where the king will land (i.e., two squares to the right or left)."
                        "The computer will automatically move the rook around the king for you.",
                        CHESS_PROGRAM_NAME,
                        MB_ICONINFORMATION | MB_OK );
                    break;

                case ID_KEY_N:      TheBoardDisplayBuffer.keyMove ( 0,  1);   break;
                case ID_KEY_NE:     TheBoardDisplayBuffer.keyMove ( 1,  1);   break;
                case ID_KEY_E:      TheBoardDisplayBuffer.keyMove ( 1,  0);   break;
                case ID_KEY_SE:     TheBoardDisplayBuffer.keyMove ( 1, -1);   break;
                case ID_KEY_S:      TheBoardDisplayBuffer.keyMove ( 0, -1);   break;
                case ID_KEY_SW:     TheBoardDisplayBuffer.keyMove (-1, -1);   break;
                case ID_KEY_W:      TheBoardDisplayBuffer.keyMove (-1,  0);   break;
                case ID_KEY_NW:     TheBoardDisplayBuffer.keyMove (-1,  1);   break;

                case ID_KEY_CHOOSE:
                    if ( insideHumanMove )
                    {
                        int x = TheBoardDisplayBuffer.getKeySelectX();
                        int y = TheBoardDisplayBuffer.getKeySelectY();

                        int editMode = MF_CHECKED &
                            GetMenuState ( HmenuMain, ID_EDIT_EDITMODE, MF_BYCOMMAND );

                        if ( editMode )
                        {
                            SQUARE square = EMPTY;

                            if ( Global_GameOverFlag || EditSquareDialog ( hwnd, square ) )
                                SendEditRequest ( x, y, square );
                        }
                        else
                        {
                            TheBoardDisplayBuffer.squareSelectedNotify ( x, y );
                        }
                    }
                    break;
            }
        }
        break;

        case WM_ERASEBKGND:
        {
            if ( !Global_AbortFlag )
            {
                RECT crect;
                GetClientRect ( hwnd, &crect );
                // Do not erase the background behind the board because the bitmaps
                // are opaque anyway.  Break the remaining area into four rectangles.
                HBRUSH hbrush = CreateSolidBrush ( CHESS_BACKGROUND_COLOR );

                // Erase above the chess board
                RECT rect;
                rect.left = crect.left;
                rect.top = crect.top;
                rect.right = SQUARE_SCREENX2(7)+1;
                rect.bottom = SQUARE_SCREENY1(7)-1;
                FillRect ( HDC(wparam), &rect, hbrush );

                // Erase below the chess board
                rect.top = SQUARE_SCREENY2(0)+1;
                rect.bottom = crect.bottom;
                FillRect ( HDC(wparam), &rect, hbrush );

                // Erase to the left of the chess board
                rect.left = crect.left;
                rect.right = SQUARE_SCREENX1(0)-1;
                rect.top = SQUARE_SCREENY1(7)-1;
                rect.bottom = SQUARE_SCREENY2(0)+1;
                FillRect ( HDC(wparam), &rect, hbrush );

                // Erase to the right of the chess board
                rect.left = SQUARE_SCREENX2(7)+1;
                rect.right = crect.right;
                rect.top = crect.top;
                rect.bottom = crect.bottom;
                FillRect ( HDC(wparam), &rect, hbrush );
                DeleteObject ( hbrush );
                result = TRUE;
            }
        }
        break;

        case WM_PAINT:
        {
            if ( !Global_AbortFlag )
            {
                PAINTSTRUCT ps;
                HDC hdc = BeginPaint ( hwnd, &ps );
                int x1 = SQUARE_CHESSX (ps.rcPaint.left);
                int x2 = SQUARE_CHESSX (ps.rcPaint.right);
                int y1 = SQUARE_CHESSY (ps.rcPaint.bottom);
                int y2 = SQUARE_CHESSY (ps.rcPaint.top);
                TheBoardDisplayBuffer.draw ( hdc, x1, x2, y1, y2 );
                ChessDisplayTextBuffer::DrawAll ( hdc );
                EndPaint ( hwnd, &ps );
                result = TRUE;
            }
        }
        break;

        case WM_LBUTTONUP:
        {
            if ( insideHumanMove )
            {
                int mouseX = LOWORD(lparam);
                int mouseY = HIWORD(lparam);

                int x = SQUARE_CHESSX ( mouseX );
                int y = SQUARE_CHESSY ( mouseY );
                if ( !TheBoardDisplayBuffer.queryWhiteView() )
                {
                    // Need to rotate the coords...

                    x = 7 - x;
                    y = 7 - y;
                }

                int editMode = MF_CHECKED &
                    GetMenuState ( HmenuMain, ID_EDIT_EDITMODE, MF_BYCOMMAND );

                if ( editMode )
                {
                    SQUARE square = EMPTY;

                    if ( Global_GameOverFlag || EditSquareDialog ( hwnd, square ) )
                        SendEditRequest ( x, y, square );
                }
                else
                {
                    TheBoardDisplayBuffer.squareSelectedNotify ( x, y );
                }
            }
        }
        break;

        case WM_RBUTTONUP:
        {
            if ( insideHumanMove )
            {
                int mouseX = LOWORD(lparam);
                int mouseY = HIWORD(lparam);

                int editMode = MF_CHECKED &
                GetMenuState ( HmenuMain, ID_EDIT_EDITMODE, MF_BYCOMMAND );

                if ( editMode )
                {
                    int x = SQUARE_CHESSX ( mouseX );
                    int y = SQUARE_CHESSY ( mouseY );

                    if ( !TheBoardDisplayBuffer.queryWhiteView() )
                    {
                        // Need to rotate the coords...

                        x = 7 - x;
                        y = 7 - y;
                    }

                    SendEditRequest ( x, y, Global_BoardEditRequest.getMostRecentSquare() );
                }
            }
        }
        break;

        case WM_DDC_ENTER_HUMAN_MOVE:
        {
            // Getting here means that we have just begun reading
            // a human player's move.  There are certain menu options
            // whose enable state should be changed now.

            insideHumanMove = true;
            CheckHumanMoveState ( insideHumanMove );
        }
        break;

        case WM_DDC_LEAVE_HUMAN_MOVE:
        {
            // Getting here means we are just now finishing with
            // a human player's move.

            insideHumanMove = false;
            CheckHumanMoveState ( insideHumanMove );
        }
        break;

        case WM_DDC_FATAL:
        {
            char *errorText = (char *)(lparam);

            MessageBox ( 
                hwnd,
                errorText,
                CHESS_PROGRAM_NAME " fatal error",
                MB_ICONEXCLAMATION | MB_OK );

            PostMessage ( hwnd, WM_QUIT, ULONG(0), ULONG(0) );
        }
        break;

        case WM_DDC_PREDICT_MATE:
            if ( !Global_SpeakMovesFlag )
            {
                sprintf ( buffer, "Mate in %d", int(wparam) );
                MessageBox ( hwnd,
                    buffer,
                    ChessProgramName,
                    MB_OK );
            }
            *(int *)(lparam) = 1;   // signals that message box is finished
            break;

        case WM_DDC_PROMOTE_PAWN:
        {
            SQUARE *promPtr = (SQUARE *)lparam;
            SQUARE localProm = EMPTY;

            DialogBoxParam ( 
                global_hInstance,
                MAKEINTRESOURCE(IDD_PROMOTE_PAWN),
                hwnd,
                DLGPROC(PromotePawn_DlgProc),
                LPARAM(&localProm) );

            if ( localProm != R_INDEX &&
                 localProm != B_INDEX &&
                 localProm != N_INDEX &&
                 localProm != Q_INDEX )
            {
                localProm = Q_INDEX;
            }

            *promPtr = localProm;
        }
        break;

        case WM_DDC_GAME_RESULT:
        {
            ChessUI_win32_gui::gameReport *report =
                (ChessUI_win32_gui::gameReport *)(lparam);

            const char *message;

            switch ( report->winner )
            {
                case SIDE_WHITE:
                    if ( report->resignation )
                    {
                        switch ( report->quitReason )
                        {
                            case qgr_lostConnection:
                                message = CHESS_PROGRAM_NAME " - Lost connection to Black";
                                break;

                            default:
                                message = CHESS_PROGRAM_NAME " - Black resigns";
                        }
                    }
                    else
                        message = CHESS_PROGRAM_NAME " - White wins";
                    break;

                case SIDE_BLACK:
                    if ( report->resignation )
                    {
                        switch ( report->quitReason )
                        {
                            case qgr_lostConnection:
                                message = CHESS_PROGRAM_NAME " - Lost connection to White";
                                break;

                            default:
                                message = CHESS_PROGRAM_NAME " - White resigns";
                        }
                    }
                    else
                        message = CHESS_PROGRAM_NAME " - Black wins";
                    break;

                case SIDE_NEITHER:
                    message = CHESS_PROGRAM_NAME " - This game is a draw";
                    break;

                default:
                    message = CHESS_PROGRAM_NAME " - !!! I'm confused !!!";
                    break;
            }

            if ( !Global_EnableGA && !Global_EnableEndgameDatabaseGen ) {
                //MessageBox ( hwnd, message, ChessProgramName, MB_OK );
                SetWindowText (hwnd, message);
            }
        }
        break;

        case WM_DDC_GAME_OVER:
        {
            insideHumanMove = true;
            Global_GameOverFlag = true;
            CheckHumanMoveState ( insideHumanMove );
        }
        break;

        case WM_DDC_DRAW_VECTOR:
        {
            int vofs1 = int(wparam);
            int vofs2 = int(lparam);
            int x1 = SQUARE_MIDX ( XPART(vofs1) - 2 );
            int y1 = SQUARE_MIDY ( YPART(vofs1) - 2 );
            int x2 = SQUARE_MIDX ( XPART(vofs2) - 2 );
            int y2 = SQUARE_MIDY ( YPART(vofs2) - 2 );
            HDC hdc = GetDC ( hwnd );
            HGDIOBJ oldpen = SelectObject ( hdc, GetStockObject(WHITE_PEN) );
            int prev = SetROP2 ( hdc, R2_XORPEN );
            MoveToEx ( hdc, x1, y1, NULL );
            LineTo ( hdc, x2, y2 );
            SetROP2 ( hdc, prev );
            SelectObject ( hdc, oldpen );
            ReleaseDC ( hwnd, hdc );
        }
        break;

        default:
            result = (BOOL) DefWindowProc ( hwnd, msg, wparam, lparam );
            break;
    }

    return result;
}


void ChessFatal ( const char *message )
{
    static int firstTime = 1;
    if ( firstTime )
    {
        firstTime = 0;
        BREAKPOINT();
        assert (false);
        PostMessage ( HwndMain, UINT(WM_DDC_FATAL), WPARAM(0), LPARAM(message) );
    }
}


void ChessBeep ( int freq, int duration )
{
//    if ( SoundEnableFlag )
//    {
//        Beep ( freq, duration );
//    }
}


//------------------------------------------------------------------


ChessDisplayTextBuffer *ChessDisplayTextBuffer::All = 0;


ChessDisplayTextBuffer::ChessDisplayTextBuffer ( 
    HWND _hwnd, int _id, int _x, int _y, marginType _margin, int _textFont, int _highlight ):
    id ( _id ),
    hwnd ( _hwnd ),
    text ( new char [MAX_CHESS_TEXT+1] ),
    next ( ChessDisplayTextBuffer::All ),
    x ( _x ),
    y ( _y ),
    margin ( _margin ),
    textFont ( _textFont ),
    highlight ( _highlight )
{
    text[0] = '\0';
    All = this;
}


ChessDisplayTextBuffer::~ChessDisplayTextBuffer()
{
    if ( text )
    {
        delete[] text;
        text = 0;
    }
}


void ChessDisplayTextBuffer::reposition()
{
    const int textWidth = 8;
    const int textHeight = 12;

    switch ( margin )
    {
        case margin_right:
        {
            x = CHESS_BITMAP_DX*8 + CHESS_LEFT_MARGIN + 2*CHESS_BOARD_BORDER_DX + 4;
        }
        break;

        case margin_bottom:
        {
            y = CHESS_BITMAP_DY*8 + 2*CHESS_BOARD_BORDER_DY + CHESS_BOTTOM_MARGIN - 2;

            // Special case for "thinking" tag:

            if ( id == STATIC_ID_THINKING )
                x = SQUARE_SCREENX1(8) - 8*textWidth;
        }
        break;

        case margin_rank:
        {
            const int coord = id - STATIC_ID_COORD_RANK_BASE;
            x = CHESS_BOARD_BORDER_DX - 1;
            y = SQUARE_MIDY(coord) - textHeight/2;
        }
        break;

        case margin_file:
        {
            const int coord = id - STATIC_ID_COORD_FILE_BASE;
            x = SQUARE_MIDX(coord) - textWidth/2;
            y = SQUARE_SCREENY1(-1) + 2;
        }
        break;
    }
}


void ChessDisplayTextBuffer::calcRegion ( RECT &rect ) const
{
    rect.left = x;
    rect.top = y;

    HDC hdc = GetDC(hwnd);
    TEXTMETRIC tm;
    HFONT oldFont = (HFONT) SelectObject ( hdc, GetStockObject(textFont) );
    GetTextMetrics ( hdc, &tm );
    SelectObject ( hdc, oldFont );
    ReleaseDC ( hwnd, hdc );

    size_t numChars = text ? strlen(text) : 0;
    rect.right  = (LONG) (x + numChars * tm.tmAveCharWidth + 3);
    rect.bottom = (LONG) (y + tm.tmHeight + tm.tmExternalLeading + 3);
}


void ChessDisplayTextBuffer::draw ( HDC hdc ) const
{
    HFONT oldFont = (HFONT) SelectObject ( hdc, GetStockObject(textFont) );
    char *p = text ? text : "";
    COLORREF color = highlight ? CHESS_TEXT_COLOR2 : CHESS_TEXT_COLOR;
    COLORREF oldTextColor = SetTextColor ( hdc, color );
    COLORREF oldBkColor   = SetBkColor ( hdc, CHESS_BACKGROUND_COLOR );
    TextOut ( hdc, x, y, p, (int)strlen(p) );
    if ( oldBkColor != CLR_INVALID ) 
        SetBkColor ( hdc, oldBkColor );
    if ( oldTextColor != CLR_INVALID )
        SetTextColor ( hdc, oldTextColor );
    SelectObject ( hdc, oldFont );
}


void ChessDisplayTextBuffer::invalidate()
{
    RECT rect;
    calcRegion ( rect );
    InvalidateRect ( hwnd, &rect, TRUE );
}


void ChessDisplayTextBuffer::setText ( const char *newText )
{
    RECT before;
    calcRegion ( before );
    strncpy ( text, newText, MAX_CHESS_TEXT );
    RECT rect;
    calcRegion ( rect );

    if ( before.right > rect.right )    
        rect.right = before.right;

    if ( before.bottom > rect.bottom )  
        rect.bottom = before.bottom;

    InvalidateRect ( hwnd, &rect, TRUE );
}


ChessDisplayTextBuffer *ChessDisplayTextBuffer::Find ( int _id )
{
    for ( ChessDisplayTextBuffer *p=All; p; p = p->next )
    {
        if ( p->id == _id )
            return p;
    }

    return 0;
}


void ChessDisplayTextBuffer::DrawAll ( HDC hdc )
{
    for ( ChessDisplayTextBuffer *p=All; p; p=p->next )
        p->draw ( hdc );
}


void ChessDisplayTextBuffer::RepositionAll ()
{
    for ( ChessDisplayTextBuffer *p=All; p; p=p->next )
        p->reposition();
}


void ChessDisplayTextBuffer::DeleteAll()
{
    while ( All )
    {
        ChessDisplayTextBuffer *next = All->next;
        delete All;
        All = next;
    }
}


void ChessDisplayTextBuffer::SetText ( int _id, const char *newText )
{
    ChessDisplayTextBuffer *p = Find(_id);
    if ( p )
        p->setText ( newText );
}
